Fix race conditions in presence tests

Fix race conditions in presence tests

defmodule Phoenix.PresenceTest do
  use ExUnit.Case, async: true
  alias Phoenix.Socket.Broadcast

  defmodule DefaultPresence do
    use Phoenix.Presence, otp_app: :phoenix
  end

  defmodule MyPresence do
    use Phoenix.Presence, otp_app: :phoenix

    def fetch(_topic, entries) do
      for {key, %{metas: metas}} <- entries, into: %{} do
        {key, %{metas: metas, extra: "extra"}}
      end
    end
  end

  Application.put_env(:phoenix, MyPresence, pubsub_server: PresPub)

  setup_all do
    {:ok, _} = Phoenix.PubSub.PG2.start_link(PresPub, pool_size: 1)
    assert {:ok, _pid} = MyPresence.start_link([])
    {:ok, pubsub: PresPub}
  end

  setup config do
    {:ok, topic: config.test}
  end

  test "default fetch/2 returns pass-through data", config do
    presences = %{"u1" => %{metas: [%{name: "u1", phx_ref: "ref"}]}}
    assert DefaultPresence.fetch(config.topic, presences) == presences
  end

  test "list/1 lists presences from tracker", config do
    assert MyPresence.list(config.topic) == %{}
    assert MyPresence.track_presence(self(), config.topic, "u1", %{name: "u1"}) == :ok
    assert %{"u1" => %{extra: "extra", metas: [%{name: "u1", phx_ref: _}]}} =
           MyPresence.list(config.topic)
  end

  test "handle_join and handle_leave broadcasts events with default fetched data",
    %{topic: topic} = config do

    pid = spawn(fn -> :timer.sleep(:infinity) end)
    Phoenix.PubSub.subscribe(config.pubsub, self(), topic)
    {:ok, _pid} = DefaultPresence.start_link(pubsub_server: config.pubsub)
    DefaultPresence.track_presence(pid, topic, "u1", %{name: "u1"})

    assert_receive %Broadcast{topic: ^topic, event: "presence_join", payload: %{
      "u1" => %{metas: [%{name: "u1", phx_ref: u1_ref}]}
    }}
    assert %{"u1" => %{metas: [%{name: "u1", phx_ref: ^u1_ref}]}} =
           DefaultPresence.list(topic)

    Process.exit(pid, :kill)
    assert_receive %Broadcast{topic: ^topic, event: "presence_leave", payload: %{
      "u1" => %{metas: [%{name: "u1", phx_ref: ^u1_ref}]}
    }}
    assert DefaultPresence.list(topic) == %{}
  end

  test "handle_join and handle_leave broadcasts events with custom fetched data",
    %{topic: topic} = config do

    pid = spawn(fn -> :timer.sleep(:infinity) end)
    Phoenix.PubSub.subscribe(config.pubsub, self(), topic)
    MyPresence.track_presence(pid, topic, "u1", %{name: "u1"})

    assert_receive %Broadcast{topic: ^topic, event: "presence_join", payload: %{
      "u1" => %{extra: "extra", metas: [%{name: "u1", phx_ref: u1_ref}]}
    }}
    assert %{"u1" => %{extra: "extra", metas: [%{name: "u1", phx_ref: ^u1_ref}]}} =
           MyPresence.list(topic)

    Process.exit(pid, :kill)
    assert_receive %Broadcast{topic: ^topic, event: "presence_leave", payload: %{
      "u1" => %{extra: "extra", metas: [%{name: "u1", phx_ref: ^u1_ref}]}
    }}
    assert MyPresence.list(topic) == %{}
  end

  test "list maintains join order when grouping", config do
    pid = spawn(fn -> :timer.sleep(:infinity) end)
    pid2 = spawn(fn -> :timer.sleep(:infinity) end)
    Phoenix.PubSub.subscribe(config.pubsub, self(), config.topic)
    MyPresence.track_presence(pid, config.topic, "u1", %{name: "1st"})
    MyPresence.track_presence(pid2, config.topic, "u1", %{name: "2nd"})

    assert %{"u1" => %{metas: [%{name: "1st"}, %{name: "2nd"}]}} =
           MyPresence.list(config.topic)
  end
end

defmodule Phoenix.PresenceTest do
  use ExUnit.Case, async: true
  alias Phoenix.Socket.Broadcast

  defmodule DefaultPresence do
    use Phoenix.Presence, otp_app: :phoenix
  end

  defmodule MyPresence do
    use Phoenix.Presence, otp_app: :phoenix

    def fetch(_topic, entries) do
      for {key, %{metas: metas}} <- entries, into: %{} do
        {key, %{metas: metas, extra: "extra"}}
      end
    end
  end

  Application.put_env(:phoenix, MyPresence, pubsub_server: PresPub)

  setup_all do
    {:ok, _} = Phoenix.PubSub.PG2.start_link(PresPub, pool_size: 1)
    assert {:ok, _pid} = MyPresence.start_link([])
    {:ok, pubsub: PresPub}
  end

  test "default fetch/2 returns pass-through data" do
    presences = %{"u1" => %{metas: [%{name: "u1", phx_ref: "ref"}]}}
    assert DefaultPresence.fetch("topic", presences) == presences
  end

  test "list/1 lists presences from tracker" do
    assert MyPresence.list("topic") == %{}
    assert MyPresence.track_presence(self(), "topic", "u1", %{name: "u1"}) == :ok
    assert %{"u1" => %{extra: "extra", metas: [%{name: "u1", phx_ref: _}]}} =
           MyPresence.list("topic")
  end

  test "handle_join and handle_leave broadcasts events with default fetched data", config do
    pid = spawn(fn -> :timer.sleep(:infinity) end)
    Phoenix.PubSub.subscribe(config.pubsub, self(), "topic")
    {:ok, _pid} = DefaultPresence.start_link(pubsub_server: config.pubsub)
    DefaultPresence.track_presence(pid, "topic", "u1", %{name: "u1"})

    assert_receive %Broadcast{topic: "topic", event: "presence_join", payload: %{
      "u1" => %{metas: [%{name: "u1", phx_ref: u1_ref}]}
    }}
    assert %{"u1" => %{metas: [%{name: "u1", phx_ref: ^u1_ref}]}} =
           DefaultPresence.list("topic")

    Process.exit(pid, :kill)
    assert_receive %Broadcast{topic: "topic", event: "presence_leave", payload: %{
      "u1" => %{metas: [%{name: "u1", phx_ref: ^u1_ref}]}
    }}
    assert DefaultPresence.list("topic") == %{}
  end

  test "handle_join and handle_leave broadcasts events with custom fetched data", config do
    pid = spawn(fn -> :timer.sleep(:infinity) end)
    Phoenix.PubSub.subscribe(config.pubsub, self(), "topic")
    MyPresence.track_presence(pid, "topic", "u1", %{name: "u1"})

    assert_receive %Broadcast{topic: "topic", event: "presence_join", payload: %{
      "u1" => %{extra: "extra", metas: [%{name: "u1", phx_ref: u1_ref}]}
    }}
    assert %{"u1" => %{extra: "extra", metas: [%{name: "u1", phx_ref: ^u1_ref}]}} =
           MyPresence.list("topic")

    Process.exit(pid, :kill)
    assert_receive %Broadcast{topic: "topic", event: "presence_leave", payload: %{
      "u1" => %{extra: "extra", metas: [%{name: "u1", phx_ref: ^u1_ref}]}
    }}
    assert MyPresence.list("topic") == %{}
  end

  test "list maintains join order when grouping", config do
    pid = spawn(fn -> :timer.sleep(:infinity) end)
    pid2 = spawn(fn -> :timer.sleep(:infinity) end)
    Phoenix.PubSub.subscribe(config.pubsub, self(), "topic")
    MyPresence.track_presence(pid, "topic", "u1", %{name: "1st"})
    MyPresence.track_presence(pid2, "topic", "u1", %{name: "2nd"})

    assert %{"u1" => %{metas: [%{name: "1st"}, %{name: "2nd"}]}} =
           MyPresence.list("topic")
  end
end


Fix the build

Fix the build

defmodule Phoenix.TemplateTest do
  use ExUnit.Case, async: true

  doctest Phoenix.Template
  alias Phoenix.Template

  @templates Path.expand("../fixtures/templates", __DIR__)

  test "engines/0" do
    assert is_map Template.engines
  end

  test "template_path_to_name/2" do
    path = "/var/www/templates/admin/users/show.html.eex"
    root = "/var/www/templates"
    assert Template.template_path_to_name(path, root) ==
           "admin/users/show.html"

    path = "/var/www/templates/users/show.html.eex"
    root = "/var/www/templates"
    assert Template.template_path_to_name(path, root) ==
           "users/show.html"

    path = "/var/www/templates/home.html.eex"
    root = "/var/www/templates"
    assert Template.template_path_to_name(path, root) ==
           "home.html"

    path = "/var/www/templates/home.html.haml"
    root = "/var/www/templates"
    assert Template.template_path_to_name(path, root) ==
           "home.html"
  end

  test "find_all/1 finds all templates in the given root" do
    templates = Template.find_all @templates
    assert Path.join(@templates, "show.html.eex") in templates

    templates = Template.find_all Path.expand("../ssl", @templates)
    assert templates == []
  end

  test "hash/1 returns the hash for the given root" do
    assert is_binary Template.hash(@templates)
  end

  test "format_encoder/1 returns the formatter for a given template" do
    assert Template.format_encoder("hello.html") == Phoenix.HTML.Engine
    assert Template.format_encoder("hello.unknown") == nil
  end

  ## On use

  defmodule View do
    use Phoenix.Template, root: Path.join(__DIR__, "../fixtures/templates")
    import Phoenix.HTML

    def render("user.json", %{name: name}) do
      %{id: 123, name: name}
    end
  end

  test "render regular function definitions" do
    assert View.render("user.json", name: "eric") ==
           %{id: 123, name: "eric"}
  end

  test "render eex templates sanitizes against xss by default" do
    assert View.render("show.html") ==
           {:safe, [[[["" | "
Show! "] | ""] | "
\n"] | "\n"]}

    assert View.render("show.html", message: "") ==
           {:safe, [[[["" | "
Show! "] | "<script>alert('xss');</script>"] |
                   "
\n"] | "\n"]}
  end

  test "render eex templates allows raw data to be injected" do
    assert View.render("safe.html", message: "") ==
           {:safe, [[["" | "Raw "] | ""] | "\n"]}
  end

  test "compiles templates from path" do
    assert View.render("show.html", message: "hello!") ==
           {:safe, [[[["" | "
Show! "] | "hello!"] | "
\n"] | "\n"]}
  end

  test "compiler adds catch-all render/2 that raises UndefinedError" do
    assert_raise Phoenix.Template.UndefinedError, ~r/Could not render "not-exists.html".*/, fn ->
      View.render("not-exists.html")
    end
  end

  test "compiler ignores missing template path" do
    defmodule OtherViews do
      use Phoenix.Template, root: Path.join(__DIR__, "not-exists")

      def template_not_found(template, _assigns) do
        "Not found: #{template}"
      end
    end

    assert OtherViews.render("foo") == "Not found: foo"
  end

  test "generates __phoenix_recompile__? function" do
    refute View.__phoenix_recompile__?
  end
end

defmodule Phoenix.TemplateTest do
  use ExUnit.Case, async: true

  doctest Phoenix.Template
  alias Phoenix.Template

  @templates Path.expand("../fixtures/templates", __DIR__)

  test "engines/0" do
    assert is_map Template.engines
  end

  test "template_path_to_name/2" do
    path = "/var/www/templates/admin/users/show.html.eex"
    root = "/var/www/templates"
    assert Template.template_path_to_name(path, root) ==
           "admin/users/show.html"

    path = "/var/www/templates/users/show.html.eex"
    root = "/var/www/templates"
    assert Template.template_path_to_name(path, root) ==
           "users/show.html"

    path = "/var/www/templates/home.html.eex"
    root = "/var/www/templates"
    assert Template.template_path_to_name(path, root) ==
           "home.html"

    path = "/var/www/templates/home.html.haml"
    root = "/var/www/templates"
    assert Template.template_path_to_name(path, root) ==
           "home.html"
  end

  test "find_all/1 finds all templates in the given root" do
    templates = Template.find_all @templates
    assert Path.join(@templates, "show.html.eex") in templates

    templates = Template.find_all Path.expand("../ssl", @templates)
    assert templates == []
  end

  test "hash/1 returns the hash for the given root" do
    assert is_binary Template.hash(@templates)
  end

  test "format_encoder/1 returns the formatter for a given template" do
    assert Template.format_encoder("hello.html") == Phoenix.HTML.Engine
    assert Template.format_encoder("hello.unknown") == nil
  end

  ## On use

  defmodule View do
    use Phoenix.Template, root: Path.join(__DIR__, "../fixtures/templates")
    import Phoenix.HTML

    def render("user.json", [name: name]) do
      %{id: 123, name: name}
    end
  end

  test "render regular function definitions" do
    assert View.render("user.json", name: "eric") ==
           %{id: 123, name: "eric"}
  end

  test "render eex templates sanitizes against xss by default" do
    assert View.render("show.html") ==
           {:safe, [[[["" | "
Show! "] | ""] | "
\n"] | "\n"]}

    assert View.render("show.html", message: "") ==
           {:safe, [[[["" | "
Show! "] | "<script>alert('xss');</script>"] |
                   "
\n"] | "\n"]}
  end

  test "render eex templates allows raw data to be injected" do
    assert View.render("safe.html", message: "") ==
           {:safe, [[["" | "Raw "] | ""] | "\n"]}
  end

  test "compiles templates from path" do
    assert View.render("show.html", message: "hello!") ==
           {:safe, [[[["" | "
Show! "] | "hello!"] | "
\n"] | "\n"]}
  end

  test "compiler adds catch-all render/2 that raises UndefinedError" do
    assert_raise Phoenix.Template.UndefinedError, ~r/Could not render "not-exists.html".*/, fn ->
      View.render("not-exists.html")
    end
  end

  test "compiler ignores missing template path" do
    defmodule OtherViews do
      use Phoenix.Template, root: Path.join(__DIR__, "not-exists")

      def template_not_found(template, _assigns) do
        "Not found: #{template}"
      end
    end

    assert OtherViews.render("foo") == "Not found: foo"
  end

  test "generates __phoenix_recompile__? function" do
    refute View.__phoenix_recompile__?
  end
end


include bootloader config in new project generator

# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
use Mix.Config

# Customize the firmware. Uncomment all or parts of the following
# to add files to the root filesystem or modify the firmware
# archive.

# config :nerves, :firmware,
#   rootfs_additions: "config/rootfs_additions",
#   fwup_conf: "config/fwup.conf"

# Import target specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
# Uncomment to use target specific configurations

# import_config "#{Mix.Project.config[:target]}.exs"

config :bootloader,
  init: [:nerves_runtime],
  app: :<%= app_name %>


Fix test.

Fix test.

defmodule SlackCoder.ErrorViewTest do
  use SlackCoder.ConnCase, async: true

  # Bring render/3 and render_to_string/3 for testing custom views
  import Phoenix.View

  describe "renders" do
    test "renders 404.html" do
      assert render_to_string(SlackCoder.ErrorView, "404.html", []) =~ ~r/404 Not Found/
    end

    test "render 500.html" do
      assert render_to_string(SlackCoder.ErrorView, "500.html", []) =~ ~r/500 That's broken/
    end

    test "render any other" do
      assert render_to_string(SlackCoder.ErrorView, "505.html", []) =~ ~r/500 That's broken/
    end
  end
end


Client can read a file into Base64 encoded chunks

defmodule Client.API do
  def handle({:message_from, message, username}) do
    # TODO: Store message in chat history
    :ok
  end

  def handle({:send_file, username, filename}) do
    chunks = read_chunks(filename, 512)
    chunks_count = Enum.count chunks
    IO.puts "#{chunks_count} chunks read"

    chunks = Enum.map(chunks, &Base.encode64/1)
    IO.inspect chunks

    # TODO:
    # ask server for where to send the files
    # connect to destination
    # send packages

    :ok
  end

  def handle(_) do
    {:error, :not_implemented}
  end

  defp read_chunks(filename, chunk_size) do
    {:ok, file} = File.open(filename, [:read])

    Stream.repeatedly(fn -> IO.binread(file, chunk_size) end)
    |> Enum.take_while(fn bytes -> not match?(:eof, bytes) end)
  end
end

defmodule Client.API do
  def handle({:message_from, message, username}) do
    # TODO: Store message in chat history
    :ok
  end

  def handle({:send_file, username, filename}) do
    IO.puts "i must read #{filename}"
    :ok
  end

  def handle(_) do
    {:error, :not_implemented}
  end
end


Strip leading column from zone codes upfront

defmodule Mix.Tasks.Parcel.IngestLandUseTable do
  use Mix.Task

  @shortdoc "Transform the Nashville.gov land use spreadsheet into a list of zone land use conditions"

  # The input data is written such that not every zone gets it's own column
  #
  #  Ag   ,  RS280   , ...
  #  or   ,  thru   , ...
  #  Ag2  ,  RS3.75 , ...
  #
  # We want be able ot translate the group (e.g. "Ag and Ag2") into the
  # list of zones they include ("AG", "AR2a").
  @zone_groups_to_zone_codes %{
    "AG and AR2a" => ["AG", "AR2a"],
    "RS80 thru RS3.75-A" => ["RS40", "RS30", "RS20", "RS15", "RS10", "RS7.5", "RS7.5-A", "RS5", "RS5-A", "RS3.75", "RS3.75-A"],
    "R80 thru R6-A" => ["R80", "R40", "R30", "R20", "R15", "R10", "R8", "R8-A", "R6", "R6-A"],
    "RM2 thru RM20-A" => ["RM2", "RM4", "RM6", "RM9", "RM9-A", "RM15", "RM15-A", "RM20", "RM20-A"],
    "RM40 thru RM100-A" => ["RM40", "RM40-A", "RM60", "RM60-A", "RM80-A", "RM100-A"],
    "M H P" => ["MHP"],
    "* S P" => ["SP"],
    "MUN and MUN-A" => ["MUN", "MUN-A"],
    "MUL and MUL-A" => ["MUL", "MUL-A"],
    "MUG and MUG-A" => ["MUG", "MUG-A"],
    "MUI and MUI-A" => ["MUI", "MUI-A"],
    "O N" => ["ON"],
    "O L" => ["OL"],
    "O G" => ["OG"],
    "OR 20 thru OR 40-A" => ["OR20", "OR20-A", "OR40", "OR40-A"],
    "ORI and ORI-A" => ["ORI", "ORI-A"],
    "C N and CN-A" => ["CN", "CN-A"],
    "CL and CL-A" => ["CL", "CL-A"],
    "CS and CS-A" => ["CS", "CS-A"],
    "C A" => ["CA"],
    "C F" => ["CF"],
    "North" => ["North"],
    "South" => ["South"],
    "West" => ["West"],
    "Central" => ["Central"],
    "S C N" => ["SCN"],
    "S C C" => ["SCC"],
    "S C R" => ["SCR"],
    "I W D" => ["IWD"],
    "I R" => ["IR"],
    "I G" => ["IG"]
  }

  def run(args) do
    {opts, [filepath]} = OptionParser.parse!(
      args,
      strict: [verbose: :boolean]
    )
    verbose = Keyword.get(opts, :verbose, false)

    shell = case verbose do
      true -> Mix.Shell.IO
      false -> Mix.Shell.Quiet
    end

    shell.info "Loading land use table..."

    lines = File.stream!(filepath)
    |> CSV.decode!(strip_fields: true)
    |> Enum.to_list

    shell.info "Loaded #{length(lines)} lines"

    shell.info "Reconstructing zone groups..."

    ordered_zone_groups = get_zone_groups lines

    shell.info "Loaded #{length(ordered_zone_groups)} zone groups"

    shell.info "Checking for unmapped zone groups..."

    unmapped_zone_groups = get_unmapped_zone_groups ordered_zone_groups
    if length(unmapped_zone_groups) > 0 do
      shell.error(
        "Found #{length(unmapped_zone_groups)} unmapped zone groups: " <>
        "#{inspect unmapped_zone_groups}"
      )
      exit 1
    else
      shell.info "All zone groups are mapped (that's good!)"
    end

    shell.info "Mapping columns to zone codes..."

    column_zone_codes = Enum.map(
      ordered_zone_groups,
      &(@zone_groups_to_zone_codes[&1])
    )

    shell.info "Reading zone categories and their column ranges"

    zone_category_to_columns = sparsify_unique(Enum.at(lines, 1))

    shell.info "Zone categories: #{ inspect zone_category_to_columns}"

    shell.info "Generating zone land use conditions..."

    land_use_condition_lines = Enum.slice(lines, 5..-1)
    |> Stream.filter(&(not land_use_category?(&1)))

    zone_land_use_conditions = Enum.flat_map(
      land_use_condition_lines,
      &(get_zoning_district_land_use_conditions(&1, column_zone_codes))
    )

    shell.info(
      "Generated #{length(zone_land_use_conditions)} zone land use conditions"
    )

    zone_land_use_conditions
  end

  @doc ~S"""
  Reconstruct the zone groups described in @zone_groups_to_zone_codes.

  The input data is written such that not every zone gets it's own column.
  Rows at index 2 through 4 include identifiers of groups of zones:

    ,          ,....
    ,Agriculture, Residential,....
    ,AG   ,  RS280   , ...
    ,and   , thru   , ...
    ,AR2a  , RS3.75 , ...

  We translate these three lines into a single list of groups, e.g.
  ["AG and AR2a", "RS280 thru RS3.75"].  This can be used with
  @zone_groups_to_zone_codes to translate the table into information per
  zone code, even though we only receive information per zone group.

  Note that these come back preserving the order of the columns they are in,
  except that they are offset one to the left.  The first column in the table
  doesn't contain useful zone group information and is dropped.

  ## Examples
  A basic example that ignores the first column and joins the data predictably:

      iex> Mix.Tasks.Parcel.IngestLandUseTable.get_zone_groups([
      ...>   ["Ignore", "all", "these"],
      ...>   ["First", "two", "rows"],
      ...>   ["", "AG", "RS80"],
      ...>   ["", "and", "thru"],
      ...>   ["", "AR2a", "RS3.75-A"]
      ...> ])
      ["AG and AR2a", "RS80 thru RS3.75-A"]

  This also handles stripping whitespace - sometimes identifiers only take
  up two lines

      iex> Mix.Tasks.Parcel.IngestLandUseTable.get_zone_groups([
      ...>   ["Ignore", "all", "these"],
      ...>   ["first", "two", "rows"],
      ...>   ["", "M", ""],
      ...>   ["", "H", "O"],
      ...>   ["", "P", "N"]
      ...> ])
      ["M H P", "O N"]
  """
  def get_zone_groups(lines) do
    zone_group_lines = Enum.slice lines, 2..4

    Stream.zip(zone_group_lines)
      |> Stream.drop(1)
      |> Stream.map(&Tuple.to_list/1)
      |> Stream.map(fn(zone_group_parts) -> Enum.join(zone_group_parts, " ") end)
      |> Enum.map(&String.trim/1)
  end

  @doc ~S"""
  Returns a list of zone groups that we haven't mapped to individual zone codes

  ## Example

  Returns anything we haven't mapped in @zone_groups_to_zone_codes

      iex> Mix.Tasks.Parcel.IngestLandUseTable.get_unmapped_zone_groups([
      ...> "GO",  # not real
      ...> "PREDS" ,  # not real
      ...> "O N"  # real
      ...> ])
      ["GO", "PREDS"]
  """
  def get_unmapped_zone_groups(zone_groups) do
    actualy_zone_groups = zone_groups

    actualy_zone_groups -- Map.keys(@zone_groups_to_zone_codes)
  end

  @doc ~S"""
  Transform a list of unique values into a mapping of the value to the columns it covers

  This is a hacky-ish function to get the ranges of columns covered by the
  zone categories in our table. One of the headers of the table is

    "",  "Agricultural", "Residential", "", "", "", ...

  Each non-empty string is a category, the empty strings after the category
  indicate that the category applies for the following columns.  This
  transforms this into a mapping of each unique value (really just our category)
  to a list of the columns it covers.

  This is useful as an intermediate step to add a "zone_category" field
  to our Zone structs.  Because we expand the zone groups in the actual
  headers into individual zone codes, we can use the result of this
  function.

  ## Example

  In the basic case this maps each name to the column range:

      iex> Mix.Tasks.Parcel.IngestLandUseTable.sparsify_unique([
      ...>   "a",
      ...>   "",
      ...>   "",
      ...>   "b",
      ...>   "c"
      ...> ])
      %{"a" => [2, 1, 0], "b" => [3], "c" => [4]}
  """
  def sparsify_unique(list, empty \\ "") do
    {sparsified, _} = Stream.with_index(list)
    |> Enum.reduce(
      {
        %{}, # Accumulator, e.g. %{"Residential" => [2, 1]}
        nil
      },
      fn({new_category, index}, {acc, current_category}) ->
        case {new_category, current_category} do
          {^empty, current_category} when current_category != nil ->
            # Add the index to the old entry
            acc = %{acc | current_category => [index | acc[current_category]]}
            {acc, current_category}
          # We've got a new name, e.g. "Mixed Use". Create a new entry
          {new_category, _} ->
            acc = Map.put(acc, new_category, [index])
            {acc, new_category}
        end
      end
    )
    sparsified
  end

  @doc ~S"""
  Returns True if the line represents a land use category

  Land use category lines have one entry in the first column - every other
  entry is blank.

    Residential Uses, "", "", "", "", ...
    Single-family, P, P, PC, ...

  ## Example

  This only returns true if the first element is populated and the remaining
  lines are blank:

      iex> Mix.Tasks.Parcel.IngestLandUseTable.land_use_category?(
      ...>   ["hello", "", "", "", ""]
      ...> )
      true

  If the first line is empty, or there are some non-empty strings, this
  returns false

      iex> Mix.Tasks.Parcel.IngestLandUseTable.land_use_category?(
      ...>   ["", "hello", "", "", "", ""]
      ...> )
      false

      iex> Mix.Tasks.Parcel.IngestLandUseTable.land_use_category?(
      ...>   ["hello", "", "", "", "", "world"]
      ...> )
      false
  """
  def land_use_category?(line) when length(line) > 0 do
    [category | rest ] = line
    (
      category != "" and
      Enum.all?(rest, &(&1 == ""))
    )
  end

  @doc ~S"""
  Return a list of `Parcel.Domain.ZoningDistrictLandUseCondition` for the line

  A `line` should comes in the format

    "Manufacturing, light", "A", "P", "", "PC", ...

  and the `column_zone_codes` include a list of Zoning District codes covered
  for each column:

    [["AG, "AR2a"], ["SP"], ...]

  ## Example

  The result is a flat list of `Parcel.Domain.ZoningDistrictLandUseCondition`.
  Empty condition codes are translated to "NP" (Not permitted).

      iex> Mix.Tasks.Parcel.IngestLandUseTable.get_zoning_district_land_use_conditions(
      ...>   ["Microbrewery", "A", "P", "", "PC"],
      ...>   [["North", "South"], ["West"], ["MUL", "MUL-A", "ON"], ["OL"]]
      ...> )
      [
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "North", land_use: "Microbrewery", land_use_condition: "A"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "South", land_use: "Microbrewery", land_use_condition: "A"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "West", land_use: "Microbrewery", land_use_condition: "P"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "MUL", land_use: "Microbrewery", land_use_condition: "NP"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "MUL-A", land_use: "Microbrewery", land_use_condition: "NP"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "ON", land_use: "Microbrewery", land_use_condition: "NP"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "OL", land_use: "Microbrewery", land_use_condition: "PC"}
      ]
  """
  def get_zoning_district_land_use_conditions(line, columns_zone_codes) do
    alias Parcel.Domain.ZoningDistrictLandUseCondition, as: ZoningCondition

    [land_use | condition_codes ] = line

    Stream.zip(condition_codes, columns_zone_codes)
    |> Enum.flat_map(fn {condition_code, column_zone_codes} ->
      condition_code = case condition_code do
        "" -> "NP"
        _ -> condition_code
      end
      # TODO: The field values should actually be objects but hacking it for now
      Enum.map(
        column_zone_codes, fn zone_code ->
          %ZoningCondition{
            land_use: land_use,
            land_use_condition: condition_code,
            zoning_district: zone_code,
          }
        end)
    end)
  end
end

defmodule Mix.Tasks.Parcel.IngestLandUseTable do
  use Mix.Task

  @shortdoc "Transform the Nashville.gov land use spreadsheet into a list of zone land use conditions"

  # The input data is written such that not every zone gets it's own column
  #
  #  Ag   ,  RS280   , ...
  #  or   ,  thru   , ...
  #  Ag2  ,  RS3.75 , ...
  #
  # We want be able ot translate the group (e.g. "Ag and Ag2") into the
  # list of zones they include ("AG", "AR2a").
  @zone_groups_to_zone_codes %{
    "AG and AR2a" => ["AG", "AR2a"],
    "RS80 thru RS3.75-A" => ["RS40", "RS30", "RS20", "RS15", "RS10", "RS7.5", "RS7.5-A", "RS5", "RS5-A", "RS3.75", "RS3.75-A"],
    "R80 thru R6-A" => ["R80", "R40", "R30", "R20", "R15", "R10", "R8", "R8-A", "R6", "R6-A"],
    "RM2 thru RM20-A" => ["RM2", "RM4", "RM6", "RM9", "RM9-A", "RM15", "RM15-A", "RM20", "RM20-A"],
    "RM40 thru RM100-A" => ["RM40", "RM40-A", "RM60", "RM60-A", "RM80-A", "RM100-A"],
    "M H P" => ["MHP"],
    "* S P" => ["SP"],
    "MUN and MUN-A" => ["MUN", "MUN-A"],
    "MUL and MUL-A" => ["MUL", "MUL-A"],
    "MUG and MUG-A" => ["MUG", "MUG-A"],
    "MUI and MUI-A" => ["MUI", "MUI-A"],
    "O N" => ["ON"],
    "O L" => ["OL"],
    "O G" => ["OG"],
    "OR 20 thru OR 40-A" => ["OR20", "OR20-A", "OR40", "OR40-A"],
    "ORI and ORI-A" => ["ORI", "ORI-A"],
    "C N and CN-A" => ["CN", "CN-A"],
    "CL and CL-A" => ["CL", "CL-A"],
    "CS and CS-A" => ["CS", "CS-A"],
    "C A" => ["CA"],
    "C F" => ["CF"],
    "North" => ["North"],
    "South" => ["South"],
    "West" => ["West"],
    "Central" => ["Central"],
    "S C N" => ["SCN"],
    "S C C" => ["SCC"],
    "S C R" => ["SCR"],
    "I W D" => ["IWD"],
    "I R" => ["IR"],
    "I G" => ["IG"]
  }

  def run(args) do
    {opts, [filepath]} = OptionParser.parse!(
      args,
      strict: [verbose: :boolean]
    )
    verbose = Keyword.get(opts, :verbose, false)

    shell = case verbose do
      true -> Mix.Shell.IO
      false -> Mix.Shell.Quiet
    end

    shell.info "Loading land use table..."

    lines = File.stream!(filepath)
    |> CSV.decode!(strip_fields: true)
    |> Enum.to_list

    shell.info "Loaded #{length(lines)} lines"

    shell.info "Reconstructing zone groups..."

    ordered_zone_groups = get_zone_groups lines

    shell.info "Loaded #{length(ordered_zone_groups)} zone groups"

    shell.info "Checking for unmapped zone groups..."

    unmapped_zone_groups = get_unmapped_zone_groups ordered_zone_groups
    if length(unmapped_zone_groups) > 0 do
      shell.error(
        "Found #{length(unmapped_zone_groups)} unmapped zone groups: " <>
        "#{inspect unmapped_zone_groups}"
      )
      exit 1
    else
      shell.info "All zone groups are mapped (that's good!)"
    end

    shell.info "Mapping columns to zone codes..."

    column_zone_codes = Enum.map(
      ordered_zone_groups,
      &(@zone_groups_to_zone_codes[&1])
    )

    shell.info "Reading zone categories and their column ranges"

    zone_category_to_columns = sparsify_unique(Enum.at(lines, 1))

    shell.info "Zone categories: #{ inspect zone_category_to_columns}"

    shell.info "Generating zone land use conditions..."

    land_use_condition_lines = Enum.slice(lines, 5..-1)
    |> Stream.filter(&(not land_use_category?(&1)))

    zone_land_use_conditions = Enum.flat_map(
      land_use_condition_lines,
      &(get_zoning_district_land_use_conditions(&1, column_zone_codes))
    )

    shell.info(
      "Generated #{length(zone_land_use_conditions)} zone land use conditions"
    )

    zone_land_use_conditions
  end

  @doc ~S"""
  Reconstruct the zone groups described in @zone_groups_to_zone_codes.

  The input data is written such that not every zone gets it's own column.
  Rows at index 2 through 4 include identifiers of groups of zones:

    ,          ,....
    Agriculture, Residential,....
    AG   ,  RS280   , ...
    and   , thru   , ...
    AR2a  , RS3.75 , ...

  We translate these three lines into a single list of groups, e.g.
  ["AG and AR2a", "RS280 thru RS3.75"].  This can be used with
  @zone_groups_to_zone_codes to translate the table into information per
  zone code, even though we only receive information per zone group.

  Note that these come back preserving the order of the columns they are in.

  ## Examples
  A basic example:

      iex> Mix.Tasks.Parcel.IngestLandUseTable.get_zone_groups([
      ...>   ["Ignore", "these"],
      ...>   ["First", "two", "rows"],
      ...>   ["AG", "RS80"],
      ...>   ["and", "thru"],
      ...>   ["AR2a", "RS3.75-A"]
      ...> ])
      ["AG and AR2a", "RS80 thru RS3.75-A"]

  This also handles stripping whitespace - sometimes identifiers only take
  up two lines

      iex> Mix.Tasks.Parcel.IngestLandUseTable.get_zone_groups([
      ...>   ["Ignore", "these"],
      ...>   ["two", "rows"],
      ...>   ["M", ""],
      ...>   ["H", "O"],
      ...>   ["P", "N"]
      ...> ])
      ["M H P", "O N"]
  """
  def get_zone_groups(lines) do
    zone_group_lines = Enum.slice lines, 2..4

    Stream.zip(zone_group_lines)
      |> Stream.map(&Tuple.to_list/1)
      |> Stream.map(fn(zone_group_parts) -> Enum.join(zone_group_parts, " ") end)
      |> Enum.map(&String.trim/1)
  end

  @doc ~S"""
  Returns a list of zone groups that we haven't mapped to individual zone codes

  ## Example

  Returns anything we haven't mapped in @zone_groups_to_zone_codes

      iex> Mix.Tasks.Parcel.IngestLandUseTable.get_unmapped_zone_groups([
      ...> "GO",  # not real
      ...> "PREDS" ,  # not real
      ...> "O N"  # real
      ...> ])
      ["GO", "PREDS"]

  This ignores the special case of an empty string (""). Right now we leave in
  the empty string as the first item to preserve the column index of the zone
  groups.

      iex> Mix.Tasks.Parcel.IngestLandUseTable.get_unmapped_zone_groups([
      ...>  "WINGARDIUM",
      ...>  "LEVIOSA",
      ...>  ""   # ignored
      ...> ])
      ["WINGARDIUM", "LEVIOSA"]
  """
  def get_unmapped_zone_groups(zone_groups) do
    actualy_zone_groups = zone_groups -- [""]

    actualy_zone_groups -- Map.keys(@zone_groups_to_zone_codes)
  end

  @doc ~S"""
  Transform a list of unique values into a mapping of the value to the columns it covers

  This is a hacky-ish function to get the ranges of columns covered by the
  zone categories in our table. One of the headers of the table is

    "",  "Agricultural", "Residential", "", "", "", ...

  Each non-empty string is a category, the empty strings after the category
  indicate that the category applies for the following columns.  This
  transforms this into a mapping of each unique value (really just our category)
  to a list of the columns it covers.

  This is useful as an intermediate step to add a "zone_category" field
  to our Zone structs.  Because we expand the zone groups in the actual
  headers into individual zone codes, we can use the result of this
  function.

  ## Example

  In the basic case this maps each name to the column range:

      iex> Mix.Tasks.Parcel.IngestLandUseTable.sparsify_unique([
      ...>   "a",
      ...>   "",
      ...>   "",
      ...>   "b",
      ...>   "c"
      ...> ])
      %{"a" => [2, 1, 0], "b" => [3], "c" => [4]}

  If the first elements are the empty value, it treats the empty value as
  it's own unique value, then continues compressing after the first non-unique
  value

      iex> Mix.Tasks.Parcel.IngestLandUseTable.sparsify_unique([
      ...>   "",
      ...>   "",
      ...>   "a",
      ...>   "",
      ...>   "b"
      ...> ])
      %{"" => [1, 0], "a" => [3, 2], "b" => [4]}
  """
  def sparsify_unique(list, empty \\ "") do
    {sparsified, _} = Stream.with_index(list)
    |> Enum.reduce(
      {
        %{}, # Accumulator, e.g. %{"Residential" => [2, 1]}
        nil
      },
      fn({new_category, index}, {acc, current_category}) ->
        case {new_category, current_category} do
          {^empty, current_category} when current_category != nil ->
            # Add the index to the old entry
            acc = %{acc | current_category => [index | acc[current_category]]}
            {acc, current_category}
          # We've got a new name, e.g. "Mixed Use". Create a new entry
          {new_category, _} ->
            acc = Map.put(acc, new_category, [index])
            {acc, new_category}
        end
      end
    )
    sparsified
  end

  @doc ~S"""
  Returns True if the line represents a land use category

  Land use category lines have one entry in the first column - every other
  entry is blank.

    Residential Uses, "", "", "", "", ...
    Single-family, P, P, PC, ...

  ## Example

  This only returns true if the first element is populated and the remaining
  lines are blank:

      iex> Mix.Tasks.Parcel.IngestLandUseTable.land_use_category?(
      ...>   ["hello", "", "", "", ""]
      ...> )
      true

  If the first line is empty, or there are some non-empty strings, this
  returns false

      iex> Mix.Tasks.Parcel.IngestLandUseTable.land_use_category?(
      ...>   ["", "hello", "", "", "", ""]
      ...> )
      false

      iex> Mix.Tasks.Parcel.IngestLandUseTable.land_use_category?(
      ...>   ["hello", "", "", "", "", "world"]
      ...> )
      false
  """
  def land_use_category?(line) when length(line) > 0 do
    [category | rest ] = line
    (
      category != "" and
      Enum.all?(rest, &(&1 == ""))
    )
  end

  @doc ~S"""
  Return a list of `Parcel.Domain.ZoningDistrictLandUseCondition` for the line

  A `line` should comes in the format

    "Manufacturing, light", "A", "P", "", "PC", ...

  and the `column_zone_codes` include a list of Zoning District codes covered
  for each column (with the first blank):

    [nil, ["AG, "AR2a"], ["SP"], ...]

  ## Example

  The result is a flat list of `Parcel.Domain.ZoningDistrictLandUseCondition`.
  Empty condition codes are translated to "NP" (Not permitted).

      iex> Mix.Tasks.Parcel.IngestLandUseTable.get_zoning_district_land_use_conditions(
      ...>   ["Microbrewery", "A", "P", "", "PC"],
      ...>   [nil, ["North", "South"], ["West"], ["MUL", "MUL-A", "ON"], ["OL"]]
      ...> )
      [
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "North", land_use: "Microbrewery", land_use_condition: "A"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "South", land_use: "Microbrewery", land_use_condition: "A"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "West", land_use: "Microbrewery", land_use_condition: "P"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "MUL", land_use: "Microbrewery", land_use_condition: "NP"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "MUL-A", land_use: "Microbrewery", land_use_condition: "NP"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "ON", land_use: "Microbrewery", land_use_condition: "NP"},
        %Parcel.Domain.ZoningDistrictLandUseCondition{zoning_district: "OL", land_use: "Microbrewery", land_use_condition: "PC"}
      ]
  """
  def get_zoning_district_land_use_conditions(line, columns_zone_codes) do
    alias Parcel.Domain.ZoningDistrictLandUseCondition, as: ZoningCondition

    [land_use | condition_codes ] = line
    [_ | columns_zone_codes ] = columns_zone_codes

    Stream.zip(condition_codes, columns_zone_codes)
    |> Enum.flat_map(fn {condition_code, column_zone_codes} ->
      condition_code = case condition_code do
        "" -> "NP"
        _ -> condition_code
      end
      # TODO: The field values should actually be objects but hacking it for now
      Enum.map(
        column_zone_codes, fn zone_code ->
          %ZoningCondition{
            land_use: land_use,
            land_use_condition: condition_code,
            zoning_district: zone_code,
          }
        end)
    end)
  end
end


fix typespec

fix typespec

defmodule Strava.Segment do
  @moduledoc """
  Segments are specific sections of road. Athletes’ times are compared on these segments and leaderboards are created.
  https://strava.github.io/api/v3/segments/
  """
  @type t :: %Strava.Segment {
    id: number,
    resource_state: number,
    name: String.t,
    activity_type: String.t,
    distance: number,
    average_grade: number,
    maximum_grade: number,
    elevation_high: number,
    elevation_low: number,
    start_latlng: list(number),
    end_latlng: list(number),
    climb_category: number,
    city: String.t,
    state: String.t,
    country: String.t,
    private: boolean,
    starred: boolean,
    created_at: String.t,
    updated_at: String.t,
    total_elevation_gain: number,
    effort_count: number,
    athlete_count: number,
    hazardous: boolean,
    star_count: number
  }
  defstruct [
    :id,
    :resource_state,
    :name,
    :activity_type,
    :distance,
    :average_grade,
    :maximum_grade,
    :elevation_high,
    :elevation_low,
    :start_latlng,
    :end_latlng,
    :climb_category,
    :city,
    :state,
    :country,
    :private,
    :starred,
    :created_at,
    :updated_at,
    :total_elevation_gain,
    :effort_count,
    :athlete_count,
    :hazardous,
    :star_count
  ]

  @doc """
  Retrieve details about a specific segment.

  ## Example

      Strava.Segment.retrieve(229781)

  More info at: https://strava.github.io/api/v3/segments/#retrieve
  """
  @spec retrieve(number) :: %Strava.Segment{}
  def retrieve(id) do
    Strava.request("segments/#{id}", as: %Strava.Segment{})
  end

  @doc """
  Retrieve an array of segment efforts, for a given segment, filtered by athlete and/or a date range.

  ## Example

      Strava.Segment.list_efforts(229781)

  More info at: https://strava.github.io/api/v3/segments/#efforts
  """
  @spec list_efforts(number) :: [%Strava.Segment{}]
  def list_efforts(id) do
    list_efforts_request(id)
  end

  @doc """
  Retrieve an array of segment efforts, for a given segment, filtered by athlete and/or a date range.

  ## Example

      Strava.Segment.list_efforts(229781, %{athlete_id: 5287})

  More info at: https://strava.github.io/api/v3/segments/#efforts
  """
  @spec list_efforts(number, map) :: [%Strava.Segment{}]
  def list_efforts(id, filters) do
    list_efforts_request(id, filters)
  end

  @doc """
  Retrieve an array of segment efforts, for a given segment, filtered by athlete and/or a date range for a given page.

  ## Example

      Strava.Segment.list_efforts(229781, %{athlete_id: 5287}, %{per_page: 10, page: 1})

  More info at: https://strava.github.io/api/v3/segments/#efforts
  """
  @spec list_efforts(number, map, map) :: [%Strava.Segment{}]
  def list_efforts(id, filters, pagination) do
    list_efforts_request(id, filters, pagination)
  end

  @doc """
  Create a stream of segment efforts, for a given segment, filtered by athlete and/or a date range.

  ## Example

      Strava.Segment.stream_efforts(229781)

  More info at: https://strava.github.io/api/v3/segments/#efforts
  """
  @spec stream_efforts(number, map) :: Enumerable.t
  def stream_efforts(id, filters \\ %{}) do
    Strava.Paginator.stream(fn(pagination) -> list_efforts(id, filters, pagination) end)
  end

  defp list_efforts_request(id, filters \\ %{}, pagination \\ %{}) do
    "segments/#{id}/all_efforts?#{URI.encode_query(Map.merge(filters, pagination))}"
    |> Strava.request(as: [%Strava.SegmentEffort{}])
    |> Enum.map(&Strava.SegmentEffort.parse/1)
  end
end

defmodule Strava.Segment do
  @moduledoc """
  Segments are specific sections of road. Athletes’ times are compared on these segments and leaderboards are created.
  https://strava.github.io/api/v3/segments/
  """
  @type t :: %Strava.Segment {
    id: number,
    resource_state: number,
    name: String.t,
    activity_type: String.t,
    distance: number,
    average_grade: number,
    maximum_grade: number,
    elevation_high: number,
    elevation_low: number,
    start_latlng: list(number),
    end_latlng: list(number),
    climb_category: number,
    city: String.t,
    state: String.t,
    country: String.t,
    private: boolean,
    starred: boolean,
    created_at: String.t,
    updated_at: String.t,
    total_elevation_gain: number,
    effort_count: number,
    athlete_count: number,
    hazardous: boolean,
    star_count: number
  }
  defstruct [
    :id,
    :resource_state,
    :name,
    :activity_type,
    :distance,
    :average_grade,
    :maximum_grade,
    :elevation_high,
    :elevation_low,
    :start_latlng,
    :end_latlng,
    :climb_category,
    :city,
    :state,
    :country,
    :private,
    :starred,
    :created_at,
    :updated_at,
    :total_elevation_gain,
    :effort_count,
    :athlete_count,
    :hazardous,
    :star_count
  ]

  @doc """
  Retrieve details about a specific segment.

  ## Example

      Strava.Segment.retrieve(229781)

  More info at: https://strava.github.io/api/v3/segments/#retrieve
  """
  @spec retrieve(number) :: %Strava.Segment{}
  def retrieve(id) do
    Strava.request("segments/#{id}", as: %Strava.Segment{})
  end

  @doc """
  Retrieve an array of segment efforts, for a given segment, filtered by athlete and/or a date range.

  ## Example

      Strava.Segment.list_efforts(229781)

  More info at: https://strava.github.io/api/v3/segments/#efforts
  """
  @spec retrieve(number) :: [%Strava.Segment{}]
  def list_efforts(id) do
    list_efforts_request(id)
  end

  @doc """
  Retrieve an array of segment efforts, for a given segment, filtered by athlete and/or a date range.

  ## Example

      Strava.Segment.list_efforts(229781, %{athlete_id: 5287})

  More info at: https://strava.github.io/api/v3/segments/#efforts
  """
  @spec list_efforts(number, map) :: [%Strava.Segment{}]
  def list_efforts(id, filters) do
    list_efforts_request(id, filters)
  end

  @doc """
  Retrieve an array of segment efforts, for a given segment, filtered by athlete and/or a date range for a given page.

  ## Example

      Strava.Segment.list_efforts(229781, %{athlete_id: 5287}, %{per_page: 10, page: 1})

  More info at: https://strava.github.io/api/v3/segments/#efforts
  """
  @spec list_efforts(number, map, map) :: [%Strava.Segment{}]
  def list_efforts(id, filters, pagination) do
    list_efforts_request(id, filters, pagination)
  end

  @doc """
  Create a stream of segment efforts, for a given segment, filtered by athlete and/or a date range.

  ## Example

      Strava.Segment.stream_efforts(229781)

  More info at: https://strava.github.io/api/v3/segments/#efforts
  """
  @spec stream_efforts(number, map) :: Enumerable.t
  def stream_efforts(id, filters \\ %{}) do
    Strava.Paginator.stream(fn(pagination) -> list_efforts(id, filters, pagination) end)
  end

  defp list_efforts_request(id, filters \\ %{}, pagination \\ %{}) do
    "segments/#{id}/all_efforts?#{URI.encode_query(Map.merge(filters, pagination))}"
    |> Strava.request(as: [%Strava.SegmentEffort{}])
    |> Enum.map(&Strava.SegmentEffort.parse/1)
  end
end


Make prod variable compatible with Distillery format

use Mix.Config

config :comeonin, :bcrypt_log_rounds, 14

config :helix_account, Helix.Account.Repo,
  size: "${HELIX_DB_POOL_SIZE}",
  username: "${HELIX_DB_USER}",
  password: "${HELIX_DB_PASS}",
  hostname: "${HELIX_DB_HOST}",
  database: "account_service"

config :guardian, Guardian,
  secret_key: "${HELIX_JWT_KEY}"

use Mix.Config

config :comeonin, :bcrypt_log_rounds, 14

config :helix_account, Helix.Account.Repo,
  size: "${HELIX_DB_POOL_SIZE}",
  username: "${HELIX_DB_USER}",
  password: "${HELIX_DB_PASS}",
  hostname: "${HELIX_DB_HOST}",
  database: "account_service"

config :guardian, Guardian,
  secret_key: System.get_env("HELIX_JWK_KEY")


Fixed typo and improper use of their in documentation

defmodule Mix.Tasks.Compile.App do
  use Mix.Task

  @recursive true

  @moduledoc """
  Writes an .app file.

  An `.app` file is a file containing Erlang terms that defines
  your application. Mix automatically generates this file based on
  your `mix.exs` configuration.

  In order to generate the file, Mix expects your application to
  have both `:app` and `:version` keys. Furthermore, you can configure
  the generated application by defining a `application` function in
  your `mix.exs` with the following options:

  * `:applications` - all applications your application depends
    on at runtime. For example, if your application depends on
    Erlang's `:crypto`, it needs to be added to this list. Most
    of your dependencies must be added as well (unless they're
    a development or test dependency). Mix and other tools use this
    list in order to properly boot your application dependencies
    before starting the application itself;

  * `:registered` - the name of all registered processes in the
    application. If your application defines a local GenServer
    with name `MyServer`, it is recommended to add `MyServer`
    to this list. It is mostly useful to detect conflicts in
    between applications that register the same names;

  * `:mod` - specify a module to invoke when the application
    is started, it must be in the format `{Mod, args}` where
    args is often an empty list. The module specified here must
    implement the callbacks defined by the `Application.Behaviour`
    behaviour;

  * `:env` - default values for the application environment.
    The application environment is one of the most common ways
    to configure applications;

  Let's see an example file:

      def application do
        [mod: {MyApp, []},
         env: [default: :value],
         applications: [:crypto]]
      end

  Besides the options above, `.app` files also expects other
  options like `:modules` and `:vsn`, but those are automatically
  filled by Mix.

  ## Command line options

  * `--force` - forces compilation regardless of modification times

  """
  def run(args) do
    {opts, _, _} = OptionParser.parse(args, switches: [force: :boolean])

    project = Mix.Project.get!
    config  = Mix.Project.config

    app     = Keyword.fetch!(config, :app)
    version = Keyword.fetch!(config, :version)

    validate_app(app)
    validate_version(version)

    path = Mix.Project.compile_path
    mods = modules_from(Path.wildcard('#{path}/*.beam')) |> Enum.sort

    target  = Path.join(path, "#{app}.app")
    sources = Mix.Project.config_files

    if opts[:force] || Mix.Utils.stale?(sources, [target]) || modules_changed?(mods, target) do
      best_guess = [
        vsn: to_char_list(version),
        modules: mods,
        applications: []
      ]

      properties = if function_exported?(project, :application, 0) do
        Keyword.merge(best_guess, project.application)
      else
        best_guess
      end

      # Ensure we always prepend the standard application dependencies
      properties = Keyword.update!(properties, :applications, fn apps ->
        [:kernel, :stdlib, :elixir] ++ apps
      end)

      properties = ensure_correct_properties(app, config, properties)
      contents   = {:application, app, properties}

      Mix.Project.build_structure(config)
      File.write!(target, :io_lib.format("~p.", [contents]))

      Mix.shell.info "Generated #{app}.app"
      :ok
    else
      :noop
    end
  end

  defp modules_changed?(mods, target) do
    case :file.consult(target) do
      {:ok, [ {:application, _app, properties} ]} ->
        properties[:registered] == mods
      _ ->
        false
    end
  end

  defp validate_app(app) when is_atom(app), do: :ok
  defp validate_app(_), do: raise(Mix.Error, message: "Expected :app to be an atom")

  defp validate_version(version) do
    unless is_binary(version) and match?({:ok, _}, Version.parse(version)) do
      raise(Mix.Error, message: "Expected :version to be a SemVer version")
    end
  end

  defp modules_from(beams) do
    Enum.map beams, &(&1 |> Path.basename |> Path.rootname(".beam") |> binary_to_atom)
  end

  defp ensure_correct_properties(app, config, properties) do
    properties
    |> Keyword.put_new(:description, to_char_list(config[:description] || app))
    |> Keyword.put_new(:registered, [])
    |> validate_properties
  end

  defp validate_properties(properties) do
    Enum.each properties, fn
      {:description, value} ->
        unless is_list(value), do:
          invalid "Application description (:description) is not a character list (got #{inspect value})"
      {:id, value} ->
        unless is_list(value), do:
          invalid "Application id (:id) is not a character list (got #{inspect value} instead)"
      {:vsn, value} ->
        unless is_list(value), do:
          invalid "Application vsn (:vsn) is not a character list (got #{inspect value} instead)"
      {:maxT, value} ->
        unless value == :infinity or is_integer(value), do:
          invalid "Application maximum time (:maxT) is not an integer or :infinity (got #{inspect value} instead)"
      {:modules, value} ->
        unless is_list(value) and Enum.all?(value, &is_atom(&1)), do:
          invalid "Application modules (:modules) should be a list of atoms (got #{inspect value} instead)"
      {:registered, value} ->
        unless is_list(value) and Enum.all?(value, &is_atom(&1)), do:
          invalid "Application registered processes (:registered) should be a list of atoms (got #{inspect value} instead)"
      {:included_applications, value} ->
        unless is_list(value) and Enum.all?(value, &is_atom(&1)), do:
          invalid "Application included applications (:included_applications) should be a list of atoms (got #{inspect value} instead)"
      {:applications, value} ->
        unless is_list(value) and Enum.all?(value, &is_atom(&1)), do:
          invalid "Application dependencies (:applications) should be a list of atoms (got #{inspect value} instead)"
      {:env, value} ->
        unless Keyword.keyword?(value), do:
          invalid "Application dependencies (:env) should be a keyword list (got #{inspect value} instead)"
      {:start_phases, value} ->
        unless Keyword.keyword?(value), do:
          invalid "Application start phases (:start_phases) should be a keyword list (got #{inspect value} instead)"
      {:mod, []} ->
        :ok
      {:mod, {module, _args}} when is_atom(module) ->
        :ok
      {:mod, value} ->
        invalid "Application callback module (:mod) should be either [] or {module, start_args} (got #{inspect value} instead)"
      _ ->
        :ok
    end

    properties
  end

  defp invalid(message) do
    raise Mix.Error, message: message
  end
end

defmodule Mix.Tasks.Compile.App do
  use Mix.Task

  @recursive true

  @moduledoc """
  Writes an .app file.

  An `.app` file is a file containing Erlang terms that defines
  your application. Mix automatically generates this file based on
  your `mix.exs` configuration.

  In order to generate the file, Mix expects your application to
  have both `:app` and `:version` keys. Furthermore, you can configure
  the generated application by defining a `application` function in
  your `mix.exs` with the following options:

  * `:applications` - all applications your application depends
    on at runtime. For example, if your application depends on
    Erlang's `:crypto`, it needs to be added to this list. Most
    of your dependencies must be added as well (unless their are
    a development or test dependnecy). Mix and other tools use this
    list in order to properly boot your application dependencies
    before starting the application itself;

  * `:registered` - the name of all registered processes in the
    application. If your application defines a local GenServer
    with name `MyServer`, it is recommended to add `MyServer`
    to this list. It is mostly useful to detect conflicts in
    between applications that register the same names;

  * `:mod` - specify a module to invoke when the application
    is started, it must be in the format `{Mod, args}` where
    args is often an empty list. The module specified here must
    implement the callbacks defined by the `Application.Behaviour`
    behaviour;

  * `:env` - default values for the application environment.
    The application environment is one of the most common ways
    to configure applications;

  Let's see an example file:

      def application do
        [mod: {MyApp, []},
         env: [default: :value],
         applications: [:crypto]]
      end

  Besides the options above, `.app` files also expects other
  options like `:modules` and `:vsn`, but those are automatically
  filled by Mix.

  ## Command line options

  * `--force` - forces compilation regardless of modification times

  """
  def run(args) do
    {opts, _, _} = OptionParser.parse(args, switches: [force: :boolean])

    project = Mix.Project.get!
    config  = Mix.Project.config

    app     = Keyword.fetch!(config, :app)
    version = Keyword.fetch!(config, :version)

    validate_app(app)
    validate_version(version)

    path = Mix.Project.compile_path
    mods = modules_from(Path.wildcard('#{path}/*.beam')) |> Enum.sort

    target  = Path.join(path, "#{app}.app")
    sources = Mix.Project.config_files

    if opts[:force] || Mix.Utils.stale?(sources, [target]) || modules_changed?(mods, target) do
      best_guess = [
        vsn: to_char_list(version),
        modules: mods,
        applications: []
      ]

      properties = if function_exported?(project, :application, 0) do
        Keyword.merge(best_guess, project.application)
      else
        best_guess
      end

      # Ensure we always prepend the standard application dependencies
      properties = Keyword.update!(properties, :applications, fn apps ->
        [:kernel, :stdlib, :elixir] ++ apps
      end)

      properties = ensure_correct_properties(app, config, properties)
      contents   = {:application, app, properties}

      Mix.Project.build_structure(config)
      File.write!(target, :io_lib.format("~p.", [contents]))

      Mix.shell.info "Generated #{app}.app"
      :ok
    else
      :noop
    end
  end

  defp modules_changed?(mods, target) do
    case :file.consult(target) do
      {:ok, [ {:application, _app, properties} ]} ->
        properties[:registered] == mods
      _ ->
        false
    end
  end

  defp validate_app(app) when is_atom(app), do: :ok
  defp validate_app(_), do: raise(Mix.Error, message: "Expected :app to be an atom")

  defp validate_version(version) do
    unless is_binary(version) and match?({:ok, _}, Version.parse(version)) do
      raise(Mix.Error, message: "Expected :version to be a SemVer version")
    end
  end

  defp modules_from(beams) do
    Enum.map beams, &(&1 |> Path.basename |> Path.rootname(".beam") |> binary_to_atom)
  end

  defp ensure_correct_properties(app, config, properties) do
    properties
    |> Keyword.put_new(:description, to_char_list(config[:description] || app))
    |> Keyword.put_new(:registered, [])
    |> validate_properties
  end

  defp validate_properties(properties) do
    Enum.each properties, fn
      {:description, value} ->
        unless is_list(value), do:
          invalid "Application description (:description) is not a character list (got #{inspect value})"
      {:id, value} ->
        unless is_list(value), do:
          invalid "Application id (:id) is not a character list (got #{inspect value} instead)"
      {:vsn, value} ->
        unless is_list(value), do:
          invalid "Application vsn (:vsn) is not a character list (got #{inspect value} instead)"
      {:maxT, value} ->
        unless value == :infinity or is_integer(value), do:
          invalid "Application maximum time (:maxT) is not an integer or :infinity (got #{inspect value} instead)"
      {:modules, value} ->
        unless is_list(value) and Enum.all?(value, &is_atom(&1)), do:
          invalid "Application modules (:modules) should be a list of atoms (got #{inspect value} instead)"
      {:registered, value} ->
        unless is_list(value) and Enum.all?(value, &is_atom(&1)), do:
          invalid "Application registered processes (:registered) should be a list of atoms (got #{inspect value} instead)"
      {:included_applications, value} ->
        unless is_list(value) and Enum.all?(value, &is_atom(&1)), do:
          invalid "Application included applications (:included_applications) should be a list of atoms (got #{inspect value} instead)"
      {:applications, value} ->
        unless is_list(value) and Enum.all?(value, &is_atom(&1)), do:
          invalid "Application dependencies (:applications) should be a list of atoms (got #{inspect value} instead)"
      {:env, value} ->
        unless Keyword.keyword?(value), do:
          invalid "Application dependencies (:env) should be a keyword list (got #{inspect value} instead)"
      {:start_phases, value} ->
        unless Keyword.keyword?(value), do:
          invalid "Application start phases (:start_phases) should be a keyword list (got #{inspect value} instead)"
      {:mod, []} ->
        :ok
      {:mod, {module, _args}} when is_atom(module) ->
        :ok
      {:mod, value} ->
        invalid "Application callback module (:mod) should be either [] or {module, start_args} (got #{inspect value} instead)"
      _ ->
        :ok
    end

    properties
  end

  defp invalid(message) do
    raise Mix.Error, message: message
  end
end


fix famware causing bot to not log in

fix famware causing bot to not log in

defmodule Farmbot.System.Network do
  @moduledoc """
    Network functionality.
  """
  require Logger
  use GenServer
  alias Farmbot.System.FS.ConfigStorage, as: CS
  alias Farmbot.System.Network.SSH
  alias Farmbot.System.Network.Ntp
  alias Farmbot.Auth

  @spec mod(atom) :: atom
  defp mod(target), do: Module.concat([Farmbot, System, target, Network])

  def init(target) do
    Logger.info ">> is starting networking"
    m = mod(target)
    {:ok, _cb} = m.start_link
    {:ok, interface_config} = get_config("interfaces")
    parse_and_start_config(interface_config, m)
    {:ok, target}
  end

  # if networking is disabled.
  defp parse_and_start_config(nil, _), do: spawn(fn ->
    Process.sleep(2000)
    {:ok, fpf} = GenServer.call(CS, {:get, Configuration, "first_party_farmware"})
    if fpf, do: Farmware.get_first_party_farmware
    Farmbot.Auth.try_log_in
  end)

  defp parse_and_start_config(config, m) do
    for {interface, settings} <- config do
        m.start_interface(interface, settings)
    end
  end

  @doc """
    Starts the network manager
  """
  def start_link(args) do
    GenServer.start_link(__MODULE__, args, name: __MODULE__)
  end

  @doc """
    Scans for wireless ssids.
  """
  @spec scan(String.t) :: [String.t]
  def scan(interface_name) do
    GenServer.call(__MODULE__, {:scan, interface_name})
  end

  @doc """
    Lists all network interfaces that Farmbot Detected.
  """
  @spec enumerate :: [String.t] | {:error, term}
  def enumerate do
    GenServer.call(__MODULE__, :enumerate)
  end

  @doc """
    Restarts networking services. This will block.
  """
  def restart do
    stop_all()
    {:ok, interface_config} = get_config("interfaces")
    m = mod(get_mod())
    parse_and_start_config(interface_config, m)
  end

  @doc """
    Starts an interface
  """
  def start_interface(interface, settings) do
    GenServer.call(__MODULE__, {:start, interface, settings}, :infinity)
  end

  @doc """
    Stops an interface
  """
  def stop_interface(interface) do
    GenServer.call(__MODULE__, {:stop, interface}, :infinity)
  end

  @doc """
    Stops all interfaces
  """
  def stop_all do
    {:ok, interfaces} = get_config("interfaces")
    if interfaces do
      for {iface, _} <- interfaces do
        stop_interface(iface)
      end
    end
  end

  @doc """
    Connected to the World Wide Web. Should be called from the
    callback module.
  """
  def on_connect(fun \\ nil) do
    # this happens because on wifi we try to do stuff before linux is
    # finished setting stuff up.
    Process.sleep(2000)
    if fun, do: fun.()
    Logger.info ">> is connected to the World Wide Web."
    Logger.info ">> is reading configurations."
    {:ok, ssh} = get_config("ssh")
    {:ok, ntp} = get_config("ntp")

    # First Party Farmware is not really a network concern but here we are...
    {:ok, fpf} = GenServer.call(CS, {:get, Configuration, "first_party_farmware"})

    if ntp do
      Logger.info ">> ntp"
      Ntp.set_time
    end

    try do
      if ssh do
        Logger.info ">> ssh"
        spawn SSH, :start_link, []
      end
    rescue
      error -> Logger.warn(">> Failed to start ssh: #{inspect error}")
    end

    try do
      if fpf, do: Farmware.get_first_party_farmware
    rescue
      error -> Logger.warn(">> Failed to install farmwares: #{inspect error}")
    end

    Logger.info ">> Login"
    r = Auth.try_log_in!
    if r == {:error, :timeout}, do: Auth.try_log_in!, else: r
  end

  @spec get_config(String.t) :: {:ok, any}
  defp get_config(key), do: GenServer.call(CS, {:get, Network, key})
  # @spec get_config() :: {:ok, false | map}
  # defp get_config, do: GenServer.call(CS, {:get, Network, :all})

  defp get_mod, do: GenServer.call(__MODULE__, :get_mod)

  # GENSERVER STUFF
  def handle_call(:get_mod, _, target), do: {:reply, target, target}
  def handle_call({:scan, interface_name}, _, target) do
     f = mod(target).scan(interface_name)
     {:reply, f, target}
  end

  def handle_call(:enumerate, _, target) do
    f = mod(target).enumerate
    {:reply, f, target}
  end

  def handle_call({:start, interface, settings}, _, target) do
    f = mod(target).start_interface(interface, settings)
    {:reply, f, target}
  end

  def handle_call({:stop, interface}, _, target) do
    f = mod(target).stop_interface(interface)
    {:reply, f, target}
  end

  def terminate(reason, target) do
    ssh_pid = Process.whereis(SSH)
    if ssh_pid do
       SSH.stop(reason)
    end
    target_pid = Process.whereis(mod(target))
    if target_pid do
      GenServer.stop(target_pid, reason)
    end
  end

  # Behavior
  @type return_type :: :ok | {:error, term}
  @callback scan(String.t) :: [String.t] | {:error, term}
  @callback enumerate() :: [String.t] | {:error, term}
  @callback start_interface(String.t, map) :: return_type
  @callback stop_interface(String.t) :: return_type
  @callback start_link :: {:ok, pid}
end

defmodule Farmbot.System.Network do
  @moduledoc """
    Network functionality.
  """
  require Logger
  use GenServer
  alias Farmbot.System.FS.ConfigStorage, as: CS
  alias Farmbot.System.Network.SSH
  alias Farmbot.System.Network.Ntp
  alias Farmbot.Auth

  @spec mod(atom) :: atom
  defp mod(target), do: Module.concat([Farmbot, System, target, Network])

  def init(target) do
    Logger.info ">> is starting networking"
    m = mod(target)
    {:ok, _cb} = m.start_link
    {:ok, interface_config} = get_config("interfaces")
    parse_and_start_config(interface_config, m)
    {:ok, target}
  end

  # if networking is disabled.
  defp parse_and_start_config(nil, _), do: spawn(fn ->
    Process.sleep(2000)
    {:ok, fpf} = GenServer.call(CS, {:get, Configuration, "first_party_farmware"})
    if fpf, do: Farmware.get_first_party_farmware
    Farmbot.Auth.try_log_in
  end)

  defp parse_and_start_config(config, m) do
    for {interface, settings} <- config do
        m.start_interface(interface, settings)
    end
  end

  @doc """
    Starts the network manager
  """
  def start_link(args) do
    GenServer.start_link(__MODULE__, args, name: __MODULE__)
  end

  @doc """
    Scans for wireless ssids.
  """
  @spec scan(String.t) :: [String.t]
  def scan(interface_name) do
    GenServer.call(__MODULE__, {:scan, interface_name})
  end

  @doc """
    Lists all network interfaces that Farmbot Detected.
  """
  @spec enumerate :: [String.t] | {:error, term}
  def enumerate do
    GenServer.call(__MODULE__, :enumerate)
  end

  @doc """
    Restarts networking services. This will block.
  """
  def restart do
    stop_all()
    {:ok, interface_config} = get_config("interfaces")
    m = mod(get_mod())
    parse_and_start_config(interface_config, m)
  end

  @doc """
    Starts an interface
  """
  def start_interface(interface, settings) do
    GenServer.call(__MODULE__, {:start, interface, settings}, :infinity)
  end

  @doc """
    Stops an interface
  """
  def stop_interface(interface) do
    GenServer.call(__MODULE__, {:stop, interface}, :infinity)
  end

  @doc """
    Stops all interfaces
  """
  def stop_all do
    {:ok, interfaces} = get_config("interfaces")
    if interfaces do
      for {iface, _} <- interfaces do
        stop_interface(iface)
      end
    end
  end

  @doc """
    Connected to the World Wide Web. Should be called from the
    callback module.
  """
  def on_connect(fun \\ nil) do
    # this happens because on wifi we try to do stuff before linux is
    # finished setting stuff up.
    Process.sleep(2000)
    if fun, do: fun.()
    Logger.info ">> is connected to the World Wide Web."
    Logger.info ">> is reading configurations."
    {:ok, ssh} = get_config("ssh")
    {:ok, ntp} = get_config("ntp")

    # First Party Farmware is not really a network concern but here we are...
    {:ok, fpf} = GenServer.call(CS, {:get, Configuration, "first_party_farmware"})

    if ntp do
      Logger.info ">> ntp"
      Ntp.set_time
    end

    if ssh do
      Logger.info ">> ssh"
      spawn SSH, :start_link, []
    end

    if fpf, do: Farmware.get_first_party_farmware

    Logger.info ">> Login"
    r = Auth.try_log_in!
    if r == {:error, :timeout}, do: Auth.try_log_in!, else: r
  end

  @spec get_config(String.t) :: {:ok, any}
  defp get_config(key), do: GenServer.call(CS, {:get, Network, key})
  # @spec get_config() :: {:ok, false | map}
  # defp get_config, do: GenServer.call(CS, {:get, Network, :all})

  defp get_mod, do: GenServer.call(__MODULE__, :get_mod)

  # GENSERVER STUFF
  def handle_call(:get_mod, _, target), do: {:reply, target, target}
  def handle_call({:scan, interface_name}, _, target) do
     f = mod(target).scan(interface_name)
     {:reply, f, target}
  end

  def handle_call(:enumerate, _, target) do
    f = mod(target).enumerate
    {:reply, f, target}
  end

  def handle_call({:start, interface, settings}, _, target) do
    f = mod(target).start_interface(interface, settings)
    {:reply, f, target}
  end

  def handle_call({:stop, interface}, _, target) do
    f = mod(target).stop_interface(interface)
    {:reply, f, target}
  end

  def terminate(reason, target) do
    ssh_pid = Process.whereis(SSH)
    if ssh_pid do
       SSH.stop(reason)
    end
    target_pid = Process.whereis(mod(target))
    if target_pid do
      GenServer.stop(target_pid, reason)
    end
  end

  # Behavior
  @type return_type :: :ok | {:error, term}
  @callback scan(String.t) :: [String.t] | {:error, term}
  @callback enumerate() :: [String.t] | {:error, term}
  @callback start_interface(String.t, map) :: return_type
  @callback stop_interface(String.t) :: return_type
  @callback start_link :: {:ok, pid}
end


swapped from import to Alias to better document origin of refactored function which was moved to helpers

defmodule Resume.Coherence.RegistrationController do
  @moduledoc """
  Handle account registration actions.

  Actions:

  * new - render the register form
  * create - create a new user account
  * edit - edit the user account
  * update - update the user account
  * delete - delete the user account
  """
  use Coherence.Web, :controller
  require Logger
  alias Coherence.ControllerHelpers, as: Helpers
  alias Resume.Repo

  alias Resume.Controllers.Helpers, as: ResumeHelpers

  plug Coherence.RequireLogin when action in ~w(show edit update delete)a
  plug Coherence.ValidateOption, :registerable
  plug :scrub_params, "registration" when action in [:create, :update]

  plug :layout_view
  plug :redirect_logged_in when action in [:new, :create]

  @doc false
  def layout_view(conn, _) do
    conn
    |> put_layout({Resume.LayoutView, "app.html"})
    |> put_view(Coherence.RegistrationView)
  end

  @doc """
  Render the new user form.
  """
  def new(conn, _params) do
    user_schema = Config.user_schema
    cs = Helpers.changeset(:registration, user_schema, user_schema.__struct__)
    conn
    |> render(:new, email: "", changeset: cs)
  end

  @doc """
  Create the new user account.

  Creates the new user account. Create and send a confirmation if
  this option is enabled.
  """
  def create(conn, %{"registration" => registration_params} = params) do
    user_schema = Config.user_schema
    cs = Helpers.changeset(:registration, user_schema, user_schema.__struct__, registration_params)
    case Config.repo.insert(cs) do
      {:ok, user} ->
        conn
        |> send_confirmation(user, user_schema)
        |> redirect_or_login(user, params, Config.allow_unconfirmed_access_for)
      {:error, changeset} ->
        conn
        |> render("new.html", changeset: changeset)
    end
  end

  defp redirect_or_login(conn, _user, params, 0) do
    redirect_to(conn, :registration_create, params)
  end
  defp redirect_or_login(conn, user, params, _) do
    Helpers.login_user(conn, user, params)
  end

  @doc """
    Show the registration page.
    Modified to also show the skills belonging to the user
  """
  def show(conn, _) do
    user = Coherence.current_user(conn) |> ResumeHelpers.preload_resume
    render(conn, "show.html", user: user)
  end

  @doc """
    Show the registered users
    View and route left unimplimented as there is no such thing as an administrator user
    Controller implimented for completeness
  """
  def index(conn, _) do
    users = User |> Repo.all |> ResumeHelpers.preload_resume
    render(conn, "index.html", users: users)
  end

  @doc """
  Edit the registration.
  """
  def edit(conn, _) do
    user = Coherence.current_user(conn)
    changeset = Helpers.changeset(:registration, user.__struct__, user)
    render(conn, "edit.html", user: user, changeset: changeset)
  end

  @doc """
  Update the registration.
  """
  def update(conn, %{"registration" => user_params} = params) do
    user_schema = Config.user_schema
    user = Coherence.current_user(conn)
    changeset = Helpers.changeset(:registration, user_schema, user, user_params)
    case Config.repo.update(changeset) do
      {:ok, user} ->
        apply(Config.auth_module, Config.update_login, [conn, user, [id_key: Config.schema_key]])
        |> put_flash(:info, "Account updated successfully.")
        |> redirect_to(:registration_update, params, user)
      {:error, changeset} ->
        render(conn, "edit.html", user: user, changeset: changeset)
    end
  end

  @doc """
  Delete a registration.
  """
  def delete(conn, params) do
    user = Coherence.current_user(conn)
    conn = Coherence.SessionController.delete(conn)
    Config.repo.delete! user
    redirect_to(conn, :registration_delete, params)
  end
end

defmodule Resume.Coherence.RegistrationController do
  @moduledoc """
  Handle account registration actions.

  Actions:

  * new - render the register form
  * create - create a new user account
  * edit - edit the user account
  * update - update the user account
  * delete - delete the user account
  """
  use Coherence.Web, :controller
  require Logger
  alias Coherence.ControllerHelpers, as: Helpers
  alias Resume.Repo

  import Resume.Controllers.Helpers

  plug Coherence.RequireLogin when action in ~w(show edit update delete)a
  plug Coherence.ValidateOption, :registerable
  plug :scrub_params, "registration" when action in [:create, :update]

  plug :layout_view
  plug :redirect_logged_in when action in [:new, :create]

  @doc false
  def layout_view(conn, _) do
    conn
    |> put_layout({Resume.LayoutView, "app.html"})
    |> put_view(Coherence.RegistrationView)
  end

  @doc """
  Render the new user form.
  """
  def new(conn, _params) do
    user_schema = Config.user_schema
    cs = Helpers.changeset(:registration, user_schema, user_schema.__struct__)
    conn
    |> render(:new, email: "", changeset: cs)
  end

  @doc """
  Create the new user account.

  Creates the new user account. Create and send a confirmation if
  this option is enabled.
  """
  def create(conn, %{"registration" => registration_params} = params) do
    user_schema = Config.user_schema
    cs = Helpers.changeset(:registration, user_schema, user_schema.__struct__, registration_params)
    case Config.repo.insert(cs) do
      {:ok, user} ->
        conn
        |> send_confirmation(user, user_schema)
        |> redirect_or_login(user, params, Config.allow_unconfirmed_access_for)
      {:error, changeset} ->
        conn
        |> render("new.html", changeset: changeset)
    end
  end

  defp redirect_or_login(conn, _user, params, 0) do
    redirect_to(conn, :registration_create, params)
  end
  defp redirect_or_login(conn, user, params, _) do
    Helpers.login_user(conn, user, params)
  end

  @doc """
    Show the registration page.
    Modified to also show the skills belonging to the user
  """
  def show(conn, _) do
    user = Coherence.current_user(conn) |> preload_resume
    render(conn, "show.html", user: user)
  end

  @doc """
    Show the registered users
    View and route left unimplimented as there is no such thing as an administrator user
    Controller implimented for completeness
  """
  def index(conn, _) do
    users = User |> Repo.all |> preload_resume
    render(conn, "index.html", users: users)
  end

  @doc """
  Edit the registration.
  """
  def edit(conn, _) do
    user = Coherence.current_user(conn)
    changeset = Helpers.changeset(:registration, user.__struct__, user)
    render(conn, "edit.html", user: user, changeset: changeset)
  end

  @doc """
  Update the registration.
  """
  def update(conn, %{"registration" => user_params} = params) do
    user_schema = Config.user_schema
    user = Coherence.current_user(conn)
    changeset = Helpers.changeset(:registration, user_schema, user, user_params)
    case Config.repo.update(changeset) do
      {:ok, user} ->
        apply(Config.auth_module, Config.update_login, [conn, user, [id_key: Config.schema_key]])
        |> put_flash(:info, "Account updated successfully.")
        |> redirect_to(:registration_update, params, user)
      {:error, changeset} ->
        render(conn, "edit.html", user: user, changeset: changeset)
    end
  end

  @doc """
  Delete a registration.
  """
  def delete(conn, params) do
    user = Coherence.current_user(conn)
    conn = Coherence.SessionController.delete(conn)
    Config.repo.delete! user
    redirect_to(conn, :registration_delete, params)
  end
end


Add child to issue

Add child to issue

  - Keep Issue-state in an Agent
  - add function state, add_child, and children

defmodule ApmIssues.Issue do
  @moduledoc """
  Define `%{ApmIssues.Issue}` and functions to modify it.
  """

  defstruct id: nil, title: "", options: %{}, children: []

  @doc """
  * `id` - Unique ID of the issue (mandatory when saving)
  * `title` - Any string (mandatory but can be an empty string)
  * `options` - optional and not specified yet

  ## Example:

      iex> pid = ApmIssues.Issue.new( 1, "My Title" )
      iex> ApmIssues.Issue.state(pid)
      %ApmIssues.Issue{options: %{}, id: 1, title: "My Title"}

      iex> pid = ApmIssues.Issue.new( 1, "One", %{state: :new} )
      iex> ApmIssues.Issue.state(pid)
      %ApmIssues.Issue{id: 1, options: %{state: :new}, title: "One"}

  """
  def new( id, title, opts \\ %{} ) do
    {:ok, pid} = Agent.start_link(fn ->
      %ApmIssues.Issue{ id: id, title: title, options: opts }
    end)
    pid
  end

  @doc """
  Get the current state of the issue
  """
  def state(pid) do
    Agent.get(pid, fn issue ->
      issue
    end)
  end

  @doc """
  Return the list of children

  ## Example
      iex> father = ApmIssues.Issue.new( "father", "Frank" )
      iex> ApmIssues.Issue.children(father)
      []
  """
  def children( pid ) do
    ApmIssues.Issue.state(pid).children
  end

  @doc """
  Add a child to the list of children

  ## Example:

      iex> father_pid = ApmIssues.Issue.new( "father", "Frank" )
      iex> son_pid    = ApmIssues.Issue.new( "son", "Dweezil" )
      iex> ApmIssues.Issue.add_child( father_pid, son_pid)
      iex> child_pid  = ApmIssues.Issue.children(father_pid) |> hd
      iex> ApmIssues.Issue.state(child_pid).title
      "Dweezil"

  """
  def add_child( parent_pid, child_pid ) do
    Agent.update(parent_pid, fn issue ->
      %ApmIssues.Issue{
        id: issue.id,
        title: issue.title,
        options: issue.options,
        children: [ child_pid | issue.children ]
      }
    end)
    parent_pid
  end

end

defmodule ApmIssues.Issue do
  @moduledoc """
  Define `%{ApmIssues.Issue}` and functions to modify it.
  """

  @doc """
  * `id` - Unique ID of the issue (mandatory when saving)
  * `title` - Any string (mandatory but can be an empty string)
  * `options` - optional and not specified yet

  ## Example:

      iex> ApmIssues.Issue.new( 1, "My Title" )
      %ApmIssues.Issue{options: %{}, id: 1, title: "My Title"}

      iex> ApmIssues.Issue.new( 1, "One", %{state: :new} )
      %ApmIssues.Issue{id: 1, options: %{state: :new}, title: "One"}

  """
  defstruct id: nil, title: "", options: %{}

  def new( id, title, opts \\ %{} ) do
    %ApmIssues.Issue{ id: id, title: title, options: opts }
  end

end


bugfix: likes

bugfix: likes

defmodule LucidboardWeb.BoardLive do
  @moduledoc "The LiveView for a Lucidboard"
  use Phoenix.LiveView
  alias Ecto.Changeset
  alias Lucidboard.{Account, Card, Column, LiveBoard, Presence, Twiddler}
  alias Lucidboard.Twiddler.Op
  alias LucidboardWeb.{BoardView, Endpoint}
  alias LucidboardWeb.Router.Helpers, as: Routes
  alias Phoenix.LiveView.Socket
  alias Phoenix.Socket.Broadcast

  def render(assigns) do
    BoardView.render("index.html", assigns)
  end

  def mount(%{user_id: nil}, socket) do
    socket =
      socket
      |> put_flash(:error, "You must be signed in")
      |> redirect(to: Routes.user_path(Endpoint, :signin_page))

    {:stop, socket}
  end

  def mount(%{id: board_id, user_id: user_id}, socket) do
    user = user_id && Account.get_user(user_id)

    case Twiddler.by_id(board_id) do
      nil ->
        {:stop, put_flash(socket, :error, "Board not found")}

      board ->
        identifier = "board:#{board.id}"
        LiveBoard.start(board.id)
        Lucidboard.subscribe(identifier)
        Presence.track(self(), identifier, user.id, %{lv_ref: socket.id})

        socket =
          socket
          |> assign(:board, board)
          |> assign(:user, user)
          |> assign(:modal_open?, false)
          |> assign(:tab, :board)
          |> assign(:column_changeset, new_column_changeset())
          |> assign(:delete_confirming_card_id, nil)

        {:ok, socket}
    end
  end

  def terminate(_reason, socket) do
    if 1 == socket |> topic() |> Presence.list() |> Map.keys() |> length() do
      LiveBoard.stop(socket.assigns.board.id)
    end
  end

  def handle_event("tab", tab, socket) when tab in ~w(board events options) do
    {:noreply, assign(socket, :tab, String.to_atom(tab))}
  end

  def handle_event("add_card", col_id, socket) do
    {:ok, %{card: new_card}} =
      {:add_and_lock_card, col_id: col_id, user_id: user_id(socket)}
      |> live_board_action(socket)

    {:noreply, presence_lock_card(socket, new_card)}
  end

  def handle_event("inline_edit", card_id, socket) do
    {:ok, card} = Op.card_by_id(socket.assigns.board, card_id)
    {:noreply, presence_lock_card(socket, card)}
  end

  def handle_event("card_save", form_data, socket) do
    {_, socket} = save_card(socket, form_data)
    {:noreply, socket}
  end

  def handle_event("modal_card_save", form_data, socket) do
    case save_card(socket, form_data) do
      {:ok, socket} -> {:noreply, assign(socket, :modal_open?, false)}
      {:invalid, socket} -> socket
    end
  end

  def handle_event("modal_card_edit", card_id, socket) do
    {:ok, card} = Op.card_by_id(socket.assigns.board, card_id)
    socket = socket |> presence_lock_card(card) |> assign(:modal_open?, true)
    {:noreply, socket}
  end

  def handle_event("card_cancel", _, socket) do
    board = socket.assigns.board

    card_id =
      Presence.get_for_session(
        topic(socket),
        socket.assigns.user.id,
        socket.id,
        :locked_card_id
      )

    {:ok, card} = Op.card_by_id(board, card_id)
    socket = socket |> finish_card_edit() |> assign(:modal_open?, false)

    delete_card_if_empty(socket, card)

    {:noreply, socket}
  end

  def handle_event("like", card_id, socket) do
    live_board_action({:like, id: card_id, user: user(socket)}, socket)
    {:noreply, socket}
  end

  def handle_event("card_delete", card_id, socket) do
    {:noreply, assign(socket, :delete_confirming_card_id, card_id)}
  end

  def handle_event("card_delete_confirmed", card_id, socket) do
    live_board_action({:delete_card, id: card_id}, socket)
    {:noreply, assign(socket, :delete_confirming_card_id, nil)}
  end

  def handle_event(
        "card_delete_cancelled",
        card_id,
        %{assigns: %{delete_confirming_card_id: card_id}} = socket
      ) do
    {:noreply, assign(socket, :delete_confirming_card_id, nil)}
  end

  def handle_event("column_edit", col_id, socket) do
    {:ok, column} = Op.column_by_id(socket.assigns.board, col_id)
    changeset = Column.changeset(column, %{})
    {:noreply, assign(socket, :column_changeset, changeset)}
  end

  def handle_event("column_save", form_data, socket) do
    cs = socket.assigns.column_changeset
    is_edit = Map.get(Changeset.apply_changes(cs), :id, nil)

    subject = if is_edit, do: cs, else: %Column{}

    case Column.changeset(subject, form_data["column"]) do
      %{valid?: true} = changeset ->
        column = Changeset.apply_changes(changeset)

        action =
          if column.id,
            do: {:update_column, id: column.id, title: column.title},
            else: {:add_column, title: column.title}

        live_board_action(action, socket)

        {:noreply, assign(socket, column_changeset: new_column_changeset())}

      invalid_changeset ->
        {:noreply, assign(socket, column_changeset: invalid_changeset)}
    end
  end

  def handle_info({:board, board}, socket) do
    {:noreply, assign(socket, :board, board)}
  end

  def handle_info(%Broadcast{event: "presence_diff"}, socket) do
    id = socket.assigns.board.id
    {:noreply, assign(socket, :online_users, Presence.list("board:#{id}"))}
  end

  def topic(%Socket{} = socket), do: "board:#{socket.assigns.board.id}"
  def topic(board_id), do: "board:#{board_id}"

  def user(%Socket{assigns: %{user: user}}), do: user

  def user_id(%Socket{assigns: %{user: %{id: id}}}), do: id

  defp finish_card_edit(socket) do
    Presence.update(
      self(),
      topic(socket),
      socket.assigns.user.id,
      &Map.drop(&1, [:locked_card_id])
    )

    assigns = Map.drop(socket.assigns, [:card_changeset])
    Map.put(socket, :assigns, assigns)
  end

  defp presence_lock_card(socket, card) do
    Presence.update(
      self(),
      topic(socket),
      socket.assigns.user.id,
      &Map.put(&1, :locked_card_id, card.id)
    )

    assign(socket, :card_changeset, Card.changeset(card))
  end

  @spec save_card(Socket.t(), map) :: {:ok | :invalid, Socket.t()}
  defp save_card(socket, form_data) do
    card = Changeset.apply_changes(socket.assigns.card_changeset)

    case Card.changeset(card, form_data["card"]) do
      %{valid?: true} = changeset ->
        card = Changeset.apply_changes(changeset)

        unless delete_card_if_empty(socket, card) do
          {:update_card, %{id: card.id, body: String.trim(card.body)}}
          |> live_board_action(socket)
        end

        {:ok, finish_card_edit(socket)}

      invalid_changeset ->
        {:invalid, assign(socket, card_changeset: invalid_changeset)}
    end
  end

  defp delete_card_if_empty(socket, card) do
    if "" == String.trim(card.body || "") do
      live_board_action({:delete_card, %{id: card.id}}, socket)
      true
    else
      false
    end
  end

  defp live_board_action(action, %Socket{} = socket) do
    live_board_action(action, socket.assigns.board.id)
  end

  defp live_board_action(action, board_id) do
    {:ok, _} = LiveBoard.call(board_id, {:action, action})
  end

  defp new_column_changeset do
    Column.changeset(%Column{}, %{})
  end
end

defmodule LucidboardWeb.BoardLive do
  @moduledoc "The LiveView for a Lucidboard"
  use Phoenix.LiveView
  alias Ecto.Changeset
  alias Lucidboard.{Account, Card, Column, LiveBoard, Presence, Twiddler, User}
  alias Lucidboard.Twiddler.Op
  alias LucidboardWeb.{BoardView, Endpoint}
  alias LucidboardWeb.Router.Helpers, as: Routes
  alias Phoenix.LiveView.Socket
  alias Phoenix.Socket.Broadcast

  def render(assigns) do
    BoardView.render("index.html", assigns)
  end

  def mount(%{user_id: nil}, socket) do
    socket =
      socket
      |> put_flash(:error, "You must be signed in")
      |> redirect(to: Routes.user_path(Endpoint, :signin_page))

    {:stop, socket}
  end

  def mount(%{id: board_id, user_id: user_id}, socket) do
    user = user_id && Account.get_user(user_id)

    case Twiddler.by_id(board_id) do
      nil ->
        {:stop, put_flash(socket, :error, "Board not found")}

      board ->
        identifier = "board:#{board.id}"
        LiveBoard.start(board.id)
        Lucidboard.subscribe(identifier)
        Presence.track(self(), identifier, user.id, %{lv_ref: socket.id})

        socket =
          socket
          |> assign(:board, board)
          |> assign(:user, user)
          |> assign(:modal_open?, false)
          |> assign(:tab, :board)
          |> assign(:column_changeset, new_column_changeset())
          |> assign(:delete_confirming_card_id, nil)

        {:ok, socket}
    end
  end

  def terminate(_reason, socket) do
    if 1 == socket |> topic() |> Presence.list() |> Map.keys() |> length() do
      LiveBoard.stop(socket.assigns.board.id)
    end
  end

  def handle_event("tab", tab, socket) when tab in ~w(board events options) do
    {:noreply, assign(socket, :tab, String.to_atom(tab))}
  end

  def handle_event("add_card", col_id, socket) do
    {:ok, %{card: new_card}} =
      {:add_and_lock_card, col_id: col_id, user_id: socket.assigns.user.id}
      |> live_board_action(socket)

    {:noreply, presence_lock_card(socket, new_card)}
  end

  def handle_event("inline_edit", card_id, socket) do
    {:ok, card} = Op.card_by_id(socket.assigns.board, card_id)
    {:noreply, presence_lock_card(socket, card)}
  end

  def handle_event("card_save", form_data, socket) do
    {_, socket} = save_card(socket, form_data)
    {:noreply, socket}
  end

  def handle_event("modal_card_save", form_data, socket) do
    case save_card(socket, form_data) do
      {:ok, socket} -> {:noreply, assign(socket, :modal_open?, false)}
      {:invalid, socket} -> socket
    end
  end

  def handle_event("modal_card_edit", card_id, socket) do
    {:ok, card} = Op.card_by_id(socket.assigns.board, card_id)
    socket = socket |> presence_lock_card(card) |> assign(:modal_open?, true)
    {:noreply, socket}
  end

  def handle_event("card_cancel", _, socket) do
    board = socket.assigns.board

    card_id =
      Presence.get_for_session(
        topic(socket),
        socket.assigns.user.id,
        socket.id,
        :locked_card_id
      )

    {:ok, card} = Op.card_by_id(board, card_id)
    socket = socket |> finish_card_edit() |> assign(:modal_open?, false)

    delete_card_if_empty(socket, card)

    {:noreply, socket}
  end

  def handle_event("like", card_id, socket) do
    {:like, id: card_id, user: %User{id: socket.assigns.user.id}}
    |> live_board_action(socket)

    {:noreply, socket}
  end

  def handle_event("card_delete", card_id, socket) do
    {:noreply, assign(socket, :delete_confirming_card_id, card_id)}
  end

  def handle_event("card_delete_confirmed", card_id, socket) do
    live_board_action({:delete_card, id: card_id}, socket)
    {:noreply, assign(socket, :delete_confirming_card_id, nil)}
  end

  def handle_event(
        "card_delete_cancelled",
        card_id,
        %{assigns: %{delete_confirming_card_id: card_id}} = socket
      ) do
    {:noreply, assign(socket, :delete_confirming_card_id, nil)}
  end

  def handle_event("column_edit", col_id, socket) do
    {:ok, column} = Op.column_by_id(socket.assigns.board, col_id)
    changeset = Column.changeset(column, %{})
    {:noreply, assign(socket, :column_changeset, changeset)}
  end

  def handle_event("column_save", form_data, socket) do
    cs = socket.assigns.column_changeset
    is_edit = Map.get(Changeset.apply_changes(cs), :id, nil)

    subject = if is_edit, do: cs, else: %Column{}

    case Column.changeset(subject, form_data["column"]) do
      %{valid?: true} = changeset ->
        column = Changeset.apply_changes(changeset)

        action =
          if column.id,
            do: {:update_column, id: column.id, title: column.title},
            else: {:add_column, title: column.title}

        live_board_action(action, socket)

        {:noreply, assign(socket, column_changeset: new_column_changeset())}

      invalid_changeset ->
        {:noreply, assign(socket, column_changeset: invalid_changeset)}
    end
  end

  def handle_info({:board, board}, socket) do
    {:noreply, assign(socket, :board, board)}
  end

  def handle_info(%Broadcast{event: "presence_diff"}, socket) do
    id = socket.assigns.board.id
    {:noreply, assign(socket, :online_users, Presence.list("board:#{id}"))}
  end

  def topic(%Socket{} = socket), do: "board:#{socket.assigns.board.id}"
  def topic(board_id), do: "board:#{board_id}"

  defp finish_card_edit(socket) do
    Presence.update(
      self(),
      topic(socket),
      socket.assigns.user.id,
      &Map.drop(&1, [:locked_card_id])
    )

    assigns = Map.drop(socket.assigns, [:card_changeset])
    Map.put(socket, :assigns, assigns)
  end

  defp presence_lock_card(socket, card) do
    Presence.update(
      self(),
      topic(socket),
      socket.assigns.user.id,
      &Map.put(&1, :locked_card_id, card.id)
    )

    assign(socket, :card_changeset, Card.changeset(card))
  end

  @spec save_card(Socket.t(), map) :: {:ok | :invalid, Socket.t()}
  defp save_card(socket, form_data) do
    card = Changeset.apply_changes(socket.assigns.card_changeset)

    case Card.changeset(card, form_data["card"]) do
      %{valid?: true} = changeset ->
        card = Changeset.apply_changes(changeset)

        unless delete_card_if_empty(socket, card) do
          {:update_card, %{id: card.id, body: String.trim(card.body)}}
          |> live_board_action(socket)
        end

        {:ok, finish_card_edit(socket)}

      invalid_changeset ->
        {:invalid, assign(socket, card_changeset: invalid_changeset)}
    end
  end

  defp delete_card_if_empty(socket, card) do
    if "" == String.trim(card.body || "") do
      live_board_action({:delete_card, %{id: card.id}}, socket)
      true
    else
      false
    end
  end

  defp live_board_action(action, %Socket{} = socket) do
    live_board_action(action, socket.assigns.board.id)
  end

  defp live_board_action(action, board_id) do
    {:ok, _} = LiveBoard.call(board_id, {:action, action})
  end

  defp new_column_changeset do
    Column.changeset(%Column{}, %{})
  end
end


Removing html entities from atom packages

defmodule DevelopmentSetup.Packages.Atom do
  @packages ~w(
    atom-beautify
    atom-elixir
    autocomplete-elixir
    file-icons
    file-types
    git-plus
    merge-conflicts
    minimap
    language-elixir
    language-vue
    linter
    linter-csslint
    linter-elixir-credo
    linter-erb
    linter-jshint
    linter-php
    linter-rubocop
    linter-ruby
    linter-scss-lint
    linter-shellcheck
    merge-conflicts
    phoenix-snippets
    pretty-json
    project-manager
    svg-preview
    todo
    rails-snippets
    remote-atom
    title-case
    xml-formatter
    atom-material-ui
    atom-material-syntax
    atom-material-syntax-dark
    atom-material-syntax-light
  )

  def install(_force) do
    if File.exists?("/Applications/Atom.app/Contents/Resources/app/apm/bin/apm") do
      IO.puts "Installing Atom packages..."

      @packages
      |> Enum.map(&install_package(&1))

      IO.puts "Done."
    else
      IO.puts "Couldn't find the Atom package manager"
    end
  end

  defp install_package(package) do
    if !File.exists?(Path.join(System.user_home, ".atom/packages/#{package}")) do
      IO.puts "    Atom - Installing #{package}"
      System.cmd("/Applications/Atom.app/Contents/Resources/app/apm/bin/apm", ["i", package])
    end
  end
end

defmodule DevelopmentSetup.Packages.Atom do
  @packages ~w(
    atom-beautify
    atom-elixir
    autocomplete-elixir
    file-icons
    file-types
    git-plus
    merge-conflicts
    minimap
    html-entities
    language-elixir
    language-vue
    linter
    linter-csslint
    linter-elixir-credo
    linter-erb
    linter-jshint
    linter-php
    linter-rubocop
    linter-ruby
    linter-scss-lint
    linter-shellcheck
    merge-conflicts
    phoenix-snippets
    pretty-json
    project-manager
    svg-preview
    todo
    rails-snippets
    remote-atom
    title-case
    xml-formatter
    atom-material-ui
    atom-material-syntax
    atom-material-syntax-dark
    atom-material-syntax-light
  )

  def install(_force) do
    if File.exists?("/Applications/Atom.app/Contents/Resources/app/apm/bin/apm") do
      IO.puts "Installing Atom packages..."

      @packages
      |> Enum.map(&install_package(&1))

      IO.puts "Done."
    else
      IO.puts "Couldn't find the Atom package manager"
    end
  end

  defp install_package(package) do
    if !File.exists?(Path.join(System.user_home, ".atom/packages/#{package}")) do
      IO.puts "    Atom - Installing #{package}"
      System.cmd("/Applications/Atom.app/Contents/Resources/app/apm/bin/apm", ["i", package])
    end
  end
end