Update stream_test.exs

Update stream_test.exs

defmodule StreamTest do
  use ExUnit.Case, async: true

  test "range is a stream" do
    range = 1..3
    stream = Stream.map(range, &(&1 * 10))
    assert [10, 20, 30] == Enum.into(stream, [])
    # there is no state in there
    assert [20, 30] == Enum.into(stream |> Stream.drop(1), [])
    assert [20] == Enum.into(stream |> Stream.drop(1) |> Stream.take(1), [])
  end

  test "URI.query_decoder returns a stream" do
    stream = URI.query_decoder("foo=1&bar=2")
    assert stream |> Stream.take(1) |> Enum.to_list() == [{"foo", "1"}]
    assert stream |> Stream.drop(1) |> Stream.take(1) |> Enum.to_list() == [{"bar", "2"}]
  end

  test "chunk_by/2" do
    # there is no range based on graphemes directly,
    # so I iterate on codepoints & conver to UTF-8
    # for later chunking. alternatively we could work
    # with
    digits = for x <- ?0..?9, do: <<x::utf8>>

    # here we'll group by chunks of digits vs. non digits
    result =
      "ab12k6b1"
      # NOTE: String.graphemes is not streaming!
      |> String.graphemes()
      |> Stream.chunk_by(&(&1 in digits))
      |> Enum.to_list()

    assert result == [["a", "b"], ["1", "2"], ["k"], ["6"], ["b"], ["1"]]
  end

  test "chunk_by/4 (no step)" do
    result =
      "FR76AB47PA26"
      |> String.graphemes()
      |> Stream.chunk_every(4)
      |> Enum.map(&Enum.join(&1))
      |> Enum.to_list()

    assert result == ["FR76", "AB47", "PA26"]
  end

  test "chunk_by/4 (with step and discard)" do
    result =
      "ABCDEF"
      |> String.graphemes()
      |> Stream.chunk_every(3, 2, :discard)
      |> Enum.map(&Enum.join(&1))
      |> Enum.to_list()

    assert result == ["ABC", "CDE"]
  end

  @tag :skip
  test "chunk_while"

  test "concat" do
    [1..2, 3..4, 5..6]
    |> Stream.concat()
    |> Enum.to_list()
    |> (&assert(&1 == [1, 2, 3, 4, 5, 6])).()
  end

  test "drop_while" do
    [1..10, 1..5]
    |> Stream.concat()
    |> Stream.drop_while(&(&1 <= 5))
    |> Enum.to_list()
    |> (&assert(&1 == [6, 7, 8, 9, 10, 1, 2, 3, 4, 5])).()
  end

  test "flat_map" do
    # useful, for instance, to generate N output items for each input item
    1..2
    |> Stream.flat_map(&[&1, 100 + &1])
    |> Enum.to_list()
    |> (&assert(&1 == [1, 101, 2, 102])).()
  end
end

defmodule StreamTest do
  use ExUnit.Case, async: true

  test "range is a stream" do
    range = 1..3
    stream = Stream.map(range, &(&1 * 10))
    assert [10, 20, 30] == Enum.into(stream, [])
    # there is no state in there
    assert [20, 30] == Enum.into(stream |> Stream.drop(1), [])
    assert [20] == Enum.into(stream |> Stream.drop(1) |> Stream.take(1), [])
  end

  test "URI.query_decoder returns a stream" do
    stream = URI.query_decoder("foo=1&bar=2")
    assert stream |> Stream.take(1) |> Enum.to_list() == [{"foo", "1"}]
    assert stream |> Stream.drop(1) |> Stream.take(1) |> Enum.to_list() == [{"bar", "2"}]
  end

  test "chunk_by/2" do
    # there is no range based on graphemes directly,
    # so I iterate on codepoints & conver to UTF-8
    # for later chunking. alternatively we could work
    # with
    digits = for x <- ?0..?9, do: <<x::utf8>>

    # here we'll group by chunks of digits vs. non digits
    result =
      "ab12k6b1"
      # NOTE: String.graphemes is not streaming!
      |> String.graphemes()
      |> Stream.chunk_by(&(&1 in digits))
      |> Enum.to_list()

    assert result == [["a", "b"], ["1", "2"], ["k"], ["6"], ["b"], ["1"]]
  end

  test "chunk_by/4 (no step)" do
    result =
      "FR76AB47PA26"
      |> String.graphemes()
      |> Stream.chunk_every(4)
      |> Enum.map(&Enum.join(&1))
      |> Enum.to_list()

    assert result == ["FR76", "AB47", "PA26"]
  end

  test "chunk_by/4 (with step and discard)" do
    result =
      "ABCDEF"
      |> String.graphemes()
      |> Stream.chunk_every(3, 2, :discard)
      |> Enum.map(&Enum.join(&1))
      |> Enum.to_list()

    assert result == ["ABC", "CDE"]
  end

  @tag :skip
  test "chunk_while"

  test "concat" do
    [1..2, 3..4, 5..6]
    |> Stream.concat()
    |> Enum.to_list()
    |> (&assert(&1 == [1, 2, 3, 4, 5, 6])).()
  end
end


Fix a typo

Fix a typo

defmodule OpenAperture.Deployer.Task.Test do
  use   ExUnit.Case

  alias OpenAperture.Deployer
  alias Deployer.DeploymentRepo
  alias Deployer.EtcdCluster
  alias Deployer.GitHub
  alias Deployer.Task.Supervisor

  setup do
    {:ok, sup} = Supervisor.start_link
    details    = %{
      source_commit_hash: "6441611f3ee2a3542b6b46ad7c82904d2cf24786",
      container_repo: "Perceptive-Cloud/cloudos-ui_docker"
    }
    {:ok, supervisor: sup, details: details}
  end

  test "SV spawns a new deployment task", %{supervisor: sup, details: details} do
    assert {:ok, pid} = Supervisor.execute_task(sup, details)
  end

  test "deploy() crashes when no deployment_repo provided" do
    assert_raise RuntimeError, fn -> Deployer.Task.deploy(%{}) end
  end

  test "deploy() failes when the source repo is incorrect" do
    repo    = DeploymentRepo.create!(%{source_repo: "some_repo"})
    details = %{deployment_repo: repo}

    :meck.new(GitHub, [:passthrough])
    :meck.expect(GitHub, :resolve_repo_url, fn(_) -> "http://dummy.url" end)
    :meck.expect(GitHub, :clone, fn(_) -> :ok end)

    assert_raise CaseClauseError, fn -> Deployer.Task.deploy(details) end
  after
    :meck.unload(GitHub)
  end

  test "deploy() fails when no units are associated with the repo" do
    repo    = DeploymentRepo.create!(%{source_repo: "some_repo"})
    details = %{deployment_repo: repo}

    :meck.new(GitHub, [:passthrough])
    :meck.expect(GitHub, :resolve_repo_url, fn(_) -> "http://dummy.url" end)
    :meck.expect(GitHub, :clone, fn(_) -> :ok end)
    :meck.expect(GitHub, :checkout, fn(_) -> :ok end)

    :meck.new(File, [:passthrough])
    :meck.expect(File, :ls!, fn(_) -> {:one, :two} end)

    assert Deployer.Task.deploy(details) ==
      {:error, "no hosts associated with the repo"}

    after
      :meck.unload(GitHub)
  end

  test "deploy() under normal conditions" do
    repo    = DeploymentRepo.create!(%{source_repo: "some_repo"})
    details = %{deployment_repo: repo}

    :meck.new(GitHub, [:passthrough])
    :meck.expect(GitHub, :resolve_repo_url, fn(_) -> "http://dummy.url" end)
    :meck.expect(GitHub, :clone, fn(_) -> :ok end)
    :meck.expect(GitHub, :checkout, fn(_) -> :ok end)

    :meck.new(File, [:passthrough])
    :meck.expect(File, :ls!, fn(_) -> {:one, :two} end)

    :meck.new(EtcdCluster, [:passthrough])
    :meck.expect(EtcdCluster, :get_host_count, fn(_) -> 314 end)
    :meck.expect(EtcdCluster, :deploy_units, fn(_, _, _) -> :units end)

    :meck.new(DeploymentRepo, [:passthrough])
    :meck.expect(DeploymentRepo, :get_etcd_cluster, fn(_) -> :cluster end)
    :meck.expect(DeploymentRepo, :get_units, fn(_) -> [:one] end)

    assert Deployer.Task.deploy(details) == :ok
  after
    [GitHub, File, EtcdCluster, DeploymentRepo] |> Enum.each &(:meck.unload(&1))
  end
end

defmodule OpenAperture.Deployer.Task.Test do
  use   ExUnit.Case

  alias OpenAperture.Deployer
  alias Deployer.DeploymentRepo
  alias Deployer.EtcdCluster
  alias Deployer.GitHub
  alias Deployer.Task.Supervisor

  setup do
    {:ok, sup} = Supervisor.start_link
    details    = %{
      source_commit_hash: "6441611f3ee2a3542b6b46ad7c82904d2cf24786",
      container_repo: "Perceptive-Cloud/cloudos-ui_docker"
    }
    {:ok, supervisor: sup, details: details}
  end

  test "SV spawns a new deployment task", %{supervisor: sup, details: details} do
    assert {:ok, pid} = Supervisor.execute_task(sup, details)
  end

  test "deploy() crashes when no deployment_repo provided" do
    assert_raise RuntimeError, fn -> Deployer.Task.deploy(%{}) end
  end

  test "deploy() failes when the source repo is incorrect" do
    repo    = DeploymentRepo.create!(%{source_repo: "some_repo"})
    details = %{deployment_repo: repo}

    :meck.new(GitHub, [:passthrough])
    :meck.expect(GitHub, :resolve_repo_url, fn(_) -> "http://dummy.url" end)
    :meck.expect(GitHub, :clone, fn(_) -> :ok end)

    assert_raise CaseClauseError, fn -> Deployer.Task.deploy(details) end
  after
    :meck.unload(Github)
  end

  test "deploy() fails when no units are associated with the repo" do
    repo    = DeploymentRepo.create!(%{source_repo: "some_repo"})
    details = %{deployment_repo: repo}

    :meck.new(GitHub, [:passthrough])
    :meck.expect(GitHub, :resolve_repo_url, fn(_) -> "http://dummy.url" end)
    :meck.expect(GitHub, :clone, fn(_) -> :ok end)
    :meck.expect(GitHub, :checkout, fn(_) -> :ok end)

    :meck.new(File, [:passthrough])
    :meck.expect(File, :ls!, fn(_) -> {:one, :two} end)

    assert Deployer.Task.deploy(details) ==
      {:error, "no hosts associated with the repo"}

    after
      :meck.unload(GitHub)
  end

  test "deploy() under normal conditions" do
    repo    = DeploymentRepo.create!(%{source_repo: "some_repo"})
    details = %{deployment_repo: repo}

    :meck.new(GitHub, [:passthrough])
    :meck.expect(GitHub, :resolve_repo_url, fn(_) -> "http://dummy.url" end)
    :meck.expect(GitHub, :clone, fn(_) -> :ok end)
    :meck.expect(GitHub, :checkout, fn(_) -> :ok end)

    :meck.new(File, [:passthrough])
    :meck.expect(File, :ls!, fn(_) -> {:one, :two} end)

    :meck.new(EtcdCluster, [:passthrough])
    :meck.expect(EtcdCluster, :get_host_count, fn(_) -> 314 end)
    :meck.expect(EtcdCluster, :deploy_units, fn(_, _, _) -> :units end)

    :meck.new(DeploymentRepo, [:passthrough])
    :meck.expect(DeploymentRepo, :get_etcd_cluster, fn(_) -> :cluster end)
    :meck.expect(DeploymentRepo, :get_units, fn(_) -> [:one] end)

    assert Deployer.Task.deploy(details) == :ok
  after
    [GitHub, File, EtcdCluster, DeploymentRepo] |> Enum.each &(:meck.unload(&1))
  end
end


:fire: Unused plug

:fire: Unused plug

defmodule AtomStyleTweaks.Router do
  use AtomStyleTweaks.Web, :router

  require Logger

  alias AtomStyleTweaks.SlidingSessionTimeout

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug :assign_current_user
    plug SlidingSessionTimeout
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/auth", AtomStyleTweaks do
    pipe_through :browser

    get "/", AuthController, :index
    get "/callback", AuthController, :callback
    get "/logout", AuthController, :delete
  end

  scope "/", AtomStyleTweaks do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
    get "/about", PageController, :about

    resources "/users", UserController, only: [:show] do
      resources "/tweaks", TweakController
    end
  end

  # Other scopes may use custom stacks.
  # scope "/api", AtomStyleTweaks do
  #   pipe_through :api
  # end

  # Fetch the current user from the session and add it to `conn.assigns`. This
  # will allow you to have access to the current user in your views with
  # `@current_user`.
  def assign_current_user(conn, _) do
    user = get_session(conn, :current_user)
    Logger.debug(fn -> "Current user = #{inspect user}" end)

    assign(conn, :current_user, user)
  end

  def log_flash(conn, _params) do
    Logger.debug(fn -> "Flash info = #{get_flash(conn, :info)}" end)
    Logger.debug(fn -> "Flash error = #{get_flash(conn, :error)}" end)

    conn
  end

  def log_assigns(conn, _params) do
    Logger.debug("=== Assigns ===")
    Enum.each(conn.assigns, fn(assign) -> Logger.debug(inspect(assign)) end)
    Logger.debug("=== End Assigns ===")

    conn
  end
end

defmodule AtomStyleTweaks.Router do
  use AtomStyleTweaks.Web, :router

  require Logger

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug :assign_current_user
    plug :authorize
    plug AtomStyleTweaks.SlidingSessionTimeout
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/auth", AtomStyleTweaks do
    pipe_through :browser

    get "/", AuthController, :index
    get "/callback", AuthController, :callback
    get "/logout", AuthController, :delete
  end

  scope "/", AtomStyleTweaks do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
    get "/about", PageController, :about

    resources "/users", UserController, only: [:show] do
      resources "/tweaks", TweakController
    end
  end

  # Other scopes may use custom stacks.
  # scope "/api", AtomStyleTweaks do
  #   pipe_through :api
  # end

  # Fetch the current user from the session and add it to `conn.assigns`. This
  # will allow you to have access to the current user in your views with
  # `@current_user`.
  def assign_current_user(conn, _) do
    user = get_session(conn, :current_user)
    Logger.debug(fn -> "Current user = #{inspect user}" end)

    assign(conn, :current_user, user)
  end

  def authorize(conn, _) do
    assign(conn, :authorized?, authorized?(get_session(conn, :current_user)))
  end

  def authorized?(nil), do: false
  def authorized?(_), do: true

  def log_flash(conn, _params) do
    Logger.debug(fn -> "Flash info = #{get_flash(conn, :info)}" end)
    Logger.debug(fn -> "Flash error = #{get_flash(conn, :error)}" end)

    conn
  end

  def log_assigns(conn, _params) do
    Logger.debug("=== Assigns ===")
    Enum.each(conn.assigns, fn(assign) -> Logger.debug(inspect(assign)) end)
    Logger.debug("=== End Assigns ===")

    conn
  end
end


add initial doc and contrived example

add initial doc and contrived example

defmodule Credo.Check.Readability.ExprPipe do
  use Credo.Check,
    base_priority: :high,
    tags: [:controversial],
    explanations: [
      check: """
      Pipes (`|>`) should not be used with case, if, or unless expressions.

      The code in this example ...

          list
          |> Enum.take(5)
          |> Enum.sort()
          |> case do
            [[_h|_t]|_] -> true
            _ -> false
          end

      ... should be refactored to look like this:

          maybe_nested_lists = list
                               |> Enum.take(5)
                               |> Enum.sort()

          case maybe_nested_lists do
            [[_h|_t]|_] = true
            _->  false
          end

      ... or create a new function

          list
          |> Enum.take(5)
          |> Enum.sort()
          |> contains_nested_list?()

      Piping to expressions is harder to read because it may obscure intention, increase cognitive load on the
      reader, and suprising to the reader per not following basic syntax principles set forth by all other
      expressions. Instead, prefer introducing variables to your code or new functions when it may be a sign that
      your function is getting too big and/or has too many concerns.

      Like all `Readability` issues, this one is not a technical concern, but you can improve the odds of others reading
      and understanding the intent of your code by making it easier to follow.
      """
    ]

  @doc false
  @impl true
  def run(%SourceFile{} = source_file, params) do
    issue_meta = IssueMeta.for(source_file, params)

    {_continue, issues} =
      Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta), {true, []})

    issues
  end

  defp traverse({:|>, meta, [_, {expr, _, _}]} = ast, {true, issues}, issue_meta)
       when expr in [:case, :if, :unless] do
    {
      ast,
      {false, issues ++ [issue_for(issue_meta, meta[:line], "|>")]}
    }
  end

  defp traverse(ast, {_, issues}, _issue_meta) do
    {ast, {true, issues}}
  end

  defp issue_for(issue_meta, line_no, trigger) do
    format_issue(
      issue_meta,
      message: "Use a variable or create a new function instead of piping to a case, if, or unless expression",
      trigger: trigger,
      line_no: line_no
    )
  end
end

defmodule Credo.Check.Readability.ExprPipe do
  use Credo.Check,
    base_priority: :high,
    tags: [:controversial],
    explanations: [
      check: """
      Pipes (`|>`) should not be used with case, if, or unless expressions.

      # TODO: Better docs here.
      """
    ]

  @doc false
  @impl true
  def run(%SourceFile{} = source_file, params) do
    issue_meta = IssueMeta.for(source_file, params)

    {_continue, issues} =
      Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta), {true, []})

    issues
  end

  defp traverse({:|>, meta, [_, {expr, _, _}]} = ast, {true, issues}, issue_meta)
       when expr in [:case, :if, :unless] do
    {
      ast,
      {false, issues ++ [issue_for(issue_meta, meta[:line], "|>")]}
    }
  end

  defp traverse(ast, {_, issues}, _issue_meta) do
    {ast, {true, issues}}
  end

  defp issue_for(issue_meta, line_no, trigger) do
    format_issue(
      issue_meta,
      message: "Use a variable or create a new function instead of piping to a case, if, or unless expression",
      trigger: trigger,
      line_no: line_no
    )
  end
end


Do not make notify public

Do not make notify public

defmodule ExUnit.EventManager do
  @moduledoc false
  @timeout 30000

  @typep manager :: {supervisor_manager :: pid, event_manager :: pid}

  # TODO: Remove support for GenEvent formatters on 2.0

  @doc """
  Starts an event manager that publishes events during the suite run.

  This is what power formatters as well as the
  internal statistics server for ExUnit.
  """
  @spec start_link() :: {:ok, manager}
  def start_link() do
    {:ok, sup} = DynamicSupervisor.start_link(strategy: :one_for_one)
    {:ok, event} = :gen_event.start_link()
    {:ok, {sup, event}}
  end

  def stop({sup, event}) do
    for {_, pid, _, _} <- DynamicSupervisor.which_children(sup) do
      GenServer.stop(pid, :normal, @timeout)
    end

    DynamicSupervisor.stop(sup)
    :gen_event.stop(event)
  end

  def add_handler({sup, event}, handler, opts) do
    if Code.ensure_loaded?(handler) and function_exported?(handler, :handle_call, 2) do
      IO.warn(
        "passing GenEvent handlers (#{inspect(handler)} in this case) in " <>
          "the :formatters option of ExUnit is deprecated, please pass a " <>
          "GenServer instead. Check the documentation for the ExUnit.Formatter " <>
          "module for more information"
      )

      :gen_event.add_handler(event, handler, opts)
    else
      DynamicSupervisor.start_child(sup, %{
        id: GenServer,
        start: {GenServer, :start_link, [handler, opts]},
        restart: :temporary
      })
    end
  end

  def suite_started(manager, opts) do
    notify(manager, {:suite_started, opts})
  end

  def suite_finished(manager, run_us, load_us) do
    notify(manager, {:suite_finished, run_us, load_us})
  end

  def module_started(manager, test_module) do
    # TODO: Remove case_started in Elixir v2.0
    notify(manager, {:case_started, Map.put(test_module, :__struct__, ExUnit.TestCase)})
    notify(manager, {:module_started, test_module})
  end

  def module_finished(manager, test_module) do
    # TODO: Remove case_finished in Elixir v2.0
    notify(manager, {:case_finished, Map.put(test_module, :__struct__, ExUnit.TestCase)})
    notify(manager, {:module_finished, test_module})
  end

  def test_started(manager, test) do
    notify(manager, {:test_started, test})
  end

  def test_finished(manager, test) do
    notify(manager, {:test_finished, test})
  end

  def max_failures_reached(manager) do
    notify(manager, :max_failures_reached)
  end

  defp notify({sup, event}, msg) do
    :gen_event.notify(event, msg)

    for {_, pid, _, _} <- Supervisor.which_children(sup) do
      GenServer.cast(pid, msg)
    end

    :ok
  end
end

defmodule ExUnit.EventManager do
  @moduledoc false
  @timeout 30000

  @typep manager :: {supervisor_manager :: pid, event_manager :: pid}

  # TODO: Remove support for GenEvent formatters on 2.0

  @doc """
  Starts an event manager that publishes events during the suite run.

  This is what power formatters as well as the
  internal statistics server for ExUnit.
  """
  @spec start_link() :: {:ok, manager}
  def start_link() do
    {:ok, sup} = DynamicSupervisor.start_link(strategy: :one_for_one)
    {:ok, event} = :gen_event.start_link()
    {:ok, {sup, event}}
  end

  def stop({sup, event}) do
    for {_, pid, _, _} <- DynamicSupervisor.which_children(sup) do
      GenServer.stop(pid, :normal, @timeout)
    end

    DynamicSupervisor.stop(sup)
    :gen_event.stop(event)
  end

  def add_handler({sup, event}, handler, opts) do
    if Code.ensure_loaded?(handler) and function_exported?(handler, :handle_call, 2) do
      IO.warn(
        "passing GenEvent handlers (#{inspect(handler)} in this case) in " <>
          "the :formatters option of ExUnit is deprecated, please pass a " <>
          "GenServer instead. Check the documentation for the ExUnit.Formatter " <>
          "module for more information"
      )

      :gen_event.add_handler(event, handler, opts)
    else
      DynamicSupervisor.start_child(sup, %{
        id: GenServer,
        start: {GenServer, :start_link, [handler, opts]},
        restart: :temporary
      })
    end
  end

  def suite_started(manager, opts) do
    notify(manager, {:suite_started, opts})
  end

  def suite_finished(manager, run_us, load_us) do
    notify(manager, {:suite_finished, run_us, load_us})
  end

  def module_started(manager, test_module) do
    # TODO: Remove case_started in Elixir v2.0
    notify(manager, {:case_started, Map.put(test_module, :__struct__, ExUnit.TestCase)})
    notify(manager, {:module_started, test_module})
  end

  def module_finished(manager, test_module) do
    # TODO: Remove case_finished in Elixir v2.0
    notify(manager, {:case_finished, Map.put(test_module, :__struct__, ExUnit.TestCase)})
    notify(manager, {:module_finished, test_module})
  end

  def test_started(manager, test) do
    notify(manager, {:test_started, test})
  end

  def test_finished(manager, test) do
    notify(manager, {:test_finished, test})
  end

  def max_failures_reached(manager) do
    notify(manager, :max_failures_reached)
  end

  def notify({sup, event} = _manager, msg) do
    :gen_event.notify(event, msg)

    for {_, pid, _, _} <- Supervisor.which_children(sup) do
      GenServer.cast(pid, msg)
    end

    :ok
  end
end


linting

linting

defmodule Stop do

  defstruct name: nil, ref: nil, timetable: [], lines: []

  @last_time_checked {2016, 7, 6}

  @last_time_checked_formatted @last_time_checked
  |> Tuple.to_list
  |> Enum.join("-")

  @moduledoc """
  Dublin Bus API
  =============

  Access to the Real Time Passenger Information (RTPI) for Dublin Bus services.

  The API are focused on retrieving bus stop and timetables

  Disclaimer
  ----------

  This service is in no way affiliated with Dublin Bus or the providers of the RTPI service.

  Data are retrieved parsing the still-in-development [RTPI](http://rtpi.ie/) site. As with any website
  scraping the html could change without notice and break the API.

  Rtpi.ie html parsing work as **#{@last_time_checked_formatted}**

  Test
  -----
  Parsing function are tested both against fixture and the actual website, this could lead to failing test if an
  internet connection is missing. It also could find if something has changed in the rtpi.ie website html.
  """

  defmodule Row do
    @moduledoc false
    defstruct [:line, :direction, :time]
  end

  @info_url "http://www.rtpi.ie/Popup_Content/WebDisplay/WebDisplay.aspx?stopRef="
  @search_url  "http://www.rtpi.ie/Text/StopResults.aspx?did=-1&search="
  @regex Regex.compile!("stopRef=(?<stop>.*)\&stopName")

  @typedoc """
  A struct that represent a row in a bus stop timetable, time could be an absolute (16:13) or relative time (5m).
  """
  @type row :: %Row{
    line: String.t,
    direction: String.t,
    time: String.t
  }

  @typedoc """
  A struct that represent a single stop, it could contain the `timetable` or the `lines` that serve the stop.

  `name` and `ref` are always available
  """
  @type stop :: %Stop{
    name: String.t,
    ref: String.t,
    lines: list(String.t),
    timetable: list(row)}

  @doc """
  Return the last time it was checked that the html parsing is still working
  """
  def last_time_checked, do: @last_time_checked

  @doc """
  Return the last time it was checked that the html parsing is still working as a string (yyyy-MM-dd)
  """
  def last_time_checked_formatted, do: @last_time_checked_formatted

  @doc"""
  Return the requested `Stop`
  """
  @spec get_info(String.t) :: stop
  def get_info(id) when is_binary(id) do
    stop = String.rjust(id, 5, ?0)
    url = @info_url <> stop

    {:ok, body} = url
    |> HTTPoison.get
    |> get_body

    timetable = body
    |> Floki.find(".gridRow")
    |> Enum.map(&parse_row/1)

    name = body
    |> Floki.find("#stopTitle")
    |> Floki.text
    |> String.split("-")
    |> List.first
    |> String.strip

    %Stop{
      ref: stop,
      name: name,
      timetable: timetable
    }
  end

  @spec get_info(Integer.t) :: stop
  def get_info(id) do
    get_info(Integer.to_string(id))
  end

  @doc """
  Return a list of `Stop` matching the `query` provided. It only returns the first ten results
  """
  @spec search(String.t) :: list(stop)
  def search(query) do
    url = @search_url <> URI.encode(query)

    body = url
    |> HTTPoison.get([], [follow_redirect: false])
    |> get_body

    case body do
      {:ok, body} ->
        case Floki.find(body, "#GridViewStopResults") do
          [] -> []
          elements -> elements
          |> hd        # get the only element
          |> Tuple.to_list
          |> List.last # look for the children of the table (tr)
          |> tl        # discard the header
          |> Enum.map(&parse_stop/1)
          |> Enum.reject(&is_nil(&1))
        end
      {:redirect, stop} ->
        [get_info(stop)]
      {:no_results} -> []
    end
  end

  defp get_body({:ok,
                 %HTTPoison.Response{status_code: 200,
                                     body: body}}) do
    {:ok, body}
  end

  defp get_body({:ok,
                 %HTTPoison.Response{status_code: 302,
                                     headers: headers}}) do
    {_, location} = headers
    |> Enum.find(fn(header) -> elem(header, 0) == "Location" end)

    %{"stop" => stop} = Regex.named_captures(@regex, location)

    {:redirect, stop}
  end

  defp get_body(_), do: {:no_results}

  defp parse_stop({"tr", _ ,
                   [{"td", _ , [line]},
                    {"td", _ , [name]}, lines_html]}) do

    lines = try do
              lines_html
              |> Floki.find("tr")
              |> Enum.map(&Floki.find(&1, "td"))
              |> Enum.map(fn x -> x |> Enum.map(&Floki.text/1) |> List.to_tuple end)
              |> Enum.into(%{})
            rescue
              _ -> %{}
            end

    %Stop{name: Floki.text(name),
          ref: line,
          lines: lines}
  end

  defp parse_stop(_), do: nil

  defp parse_row({"tr", _,
                  [{"td", [{"class", "gridServiceItem"}, _], [line]}, _,
                   {"td", [{"class", "gridDestinationItem"}, _],
                    [{"span", _, [direction]}]}, _,
                   {"td", [{"class", "gridTimeItem"}, _], [time]}, _]}) do

    %Row{time: time,
         line: line,
         direction: direction}
  end
end

defmodule Stop do

  defstruct name: nil, ref: nil, timetable: [], lines: []

  @last_time_checked {2016, 7, 6}

  @last_time_checked_formatted @last_time_checked
  |> Tuple.to_list
  |> Enum.join("-")

  @moduledoc """
  Dublin Bus API
  =============

  Access to the Real Time Passenger Information (RTPI) for Dublin Bus services.

  The API are focused on retrieving bus stop and timetables

  Disclaimer
  ----------

  This service is in no way affiliated with Dublin Bus or the providers of the RTPI service.

  Data are retrieved parsing the still-in-development [RTPI](http://rtpi.ie/) site. As with any website
  scraping the html could change without notice and break the API.

  Rtpi.ie html parsing work as **#{@last_time_checked_formatted}**

  Test
  -----
  Parsing function are tested both against fixture and the actual website, this could lead to failing test if an
  internet connection is missing. It also could find if something has changed in the rtpi.ie website html.
  """

  defmodule Row do
    @moduledoc false
    defstruct [:line, :direction, :time]
  end

  @info_url "http://www.rtpi.ie/Popup_Content/WebDisplay/WebDisplay.aspx?stopRef="
  @search_url  "http://www.rtpi.ie/Text/StopResults.aspx?did=-1&search="
  @regex Regex.compile!("stopRef=(?<stop>.*)\&stopName")

  @typedoc """
  A struct that represent a row in a bus stop timetable, time could be an absolute (16:13) or relative time (5m).
  """
  @type row :: %Row{
    line: String.t,
    direction: String.t,
    time: String.t
  }

  @typedoc """
  A struct that represent a single stop, it could contain the `timetable` or the `lines` that serve the stop.

  `name` and `ref` are always available
  """
  @type stop :: %Stop{
    name: String.t,
    ref: String.t,
    lines: list(String.t),
    timetable: list(row)}

  @doc """
  Return the last time it was checked that the html parsing is still working
  """
  def last_time_checked(), do: @last_time_checked

  @doc """
  Return the last time it was checked that the html parsing is still working as a string (yyyy-MM-dd)
  """
  def last_time_checked_formatted, do: @last_time_checked_formatted

  @doc"""
  Return the requested `Stop`
  """
  @spec get_info(String.t) :: stop
  def get_info(id) when is_binary(id) do
    stop = String.rjust(id,5,?0)
    url = @info_url <> stop

    {:ok, body} = url
    |> HTTPoison.get
    |> get_body

    timetable = body
    |> Floki.find(".gridRow")
    |> Enum.map(&parse_row/1)

    name = body
    |> Floki.find("#stopTitle")
    |> Floki.text
    |> String.split("-")
    |> List.first
    |> String.strip

    %Stop{
      ref: stop,
      name: name,
      timetable: timetable
    }
  end

  @spec get_info(Integer.t) :: stop
  def get_info(id) do
    get_info(Integer.to_string(id))
  end

  @doc """
  Return a list of `Stop` matching the `query` provided. It only returns the first ten results
  """
  @spec search(String.t) :: list(stop)
  def search(query) do
    url = @search_url <> URI.encode(query)

    body = url
    |> HTTPoison.get([], [follow_redirect: false])
    |> get_body

    case body do
      {:ok, body} ->
        case Floki.find(body, "#GridViewStopResults") do
          [] -> []
          elements -> elements
            |> hd        # get the only element
            |> Tuple.to_list
            |> List.last # look for the children of the table (tr)
            |> tl        # discard the header
            |> Enum.map(&parse_stop/1)
            |> Enum.reject(&is_nil(&1))
        end
      {:redirect, stop} ->
        [get_info(stop)]
      {:no_results} -> []
    end
  end

  defp get_body({:ok,
                 %HTTPoison.Response{status_code: 200,
                                     body: body}}) do
    {:ok, body}
  end

  defp get_body({:ok,
                 %HTTPoison.Response{status_code: 302,
                                     headers: headers}}) do
    {_, location} = headers
    |> Enum.find(fn(header) -> elem(header, 0) == "Location" end)

    %{"stop" => stop} = Regex.named_captures(@regex, location)

    {:redirect, stop}
  end

  defp get_body(_), do: {:no_results}

  defp parse_stop({"tr", _ ,
                   [{"td",_ , [line]},
                    {"td",_ , [name]}, lines_html]}) do

    lines = try do
              lines_html
              |> Floki.find("tr")
              |> Enum.map(&Floki.find(&1,"td"))
              |> Enum.map(fn x -> x |> Enum.map(&Floki.text/1) |> List.to_tuple end)
              |> Enum.into(%{})
            rescue
              _ -> %{}
            end

    %Stop{name: Floki.text(name),
          ref: line,
          lines: lines}
  end

  defp parse_stop(_), do: nil

  defp parse_row({"tr", _,
                  [{"td", [{"class", "gridServiceItem"}, _], [line]}, _,
                   {"td", [{"class", "gridDestinationItem"}, _],
                    [{"span", _, [direction]}]}, _,
                   {"td", [{"class", "gridTimeItem"}, _], [time]}, _]}) do

    %Row{time: time,
         line: line,
         direction: direction}
  end
end


always reset defaults instead of if old params

defmodule Farmbot.Target.ConfigMigration.BeforeNetwork do
  @moduledoc "Init module for migrating the old JSON based config."
  use GenServer
  alias Farmbot.System.ConfigStorage
  use Farmbot.Logger

  @data_path Application.get_env(:farmbot, :data_path)

  @doc false
  def start_link(_, _) do
    GenServer.start_link(__MODULE__, [], [name: __MODULE__])
  end

  def init([]) do

    old_farmware_dir = Path.join(@data_path, "farmware")
    old_config_json_file = Path.join(@data_path, "config.json")
    old_secret_file = Path.join(@data_path, "secret")

    if File.exists?(old_config_json_file) do
      File.rm_rf(old_farmware_dir)
      Logger.busy 1, "Migrating json based config."
      with :ok <- migrate_config_file(old_config_json_file),
           :ok <- migrate_secret(old_secret_file),
           _   <- migrate_fw()
      do
        Logger.success 1, "Successfully migrated."
        :ignore
      else
        {:error, step, sub_step, reason} ->
          msg = "Migration failed at step: #{step} substep: #{sub_step} reason: #{inspect reason}"
          Logger.error 1, msg
          {:stop, msg}
      end
    else
      :ignore
    end
  end

  def migrate_fw do
    hw = ConfigStorage.get_config_value(:string, "settings", "firmware_hardware")
    Farmbot.Firmware.UartHandler.Update.maybe_update_firmware(hw)
  end

  def migrate_config_file(filename) do
    with {:file_read, {:ok, file}} <- {:file_read,     File.read(filename)},
    {:json_decode,    {:ok, data}} <- {:json_decode,   Poison.decode(file)},
    {:authorization,  {:ok, auth}} <- {:authorization, Map.fetch(data, "authorization")},
    {:authorization,  :ok}         <- {:authorization, migrate_auth(auth)},
    {:configuration,  {:ok, conf}} <- {:configuration, Map.fetch(data, "configuration")},
    {:configuration,  :ok}         <- {:configuration, migrate_configuration(conf)},
    {:hardware,       {:ok, hw  }} <- {:hardware,      Map.fetch(data, "hardware")},
    {:hardware,       :ok}         <- {:hardware,      migrate_hw(hw)},
    {:network,        {:ok, net }} <- {:network,       Map.fetch(data, "network")},
    {:network,        :ok}         <- {:network,       migrate_network(net)} do
      Logger.success 1, "JSON config migrated"
      :ok
    else
      {step, {:error, reason}} -> {:error, :migrate_config_file, step, reason}
    end
  end

  def migrate_secret(filename) do
    with {:ok, bin_term} <- File.read(filename) do
      bin = :erlang.binary_to_term(bin_term)
      ConfigStorage.update_config_value(:string, "authorization", "password", bin)
      File.rm(filename)
    else
      {:error, reason} ->
        {:error, :migrate_secret_file,  :file_read, reason}
    end
  end

  defp migrate_auth(%{"server" => server}) do
    ConfigStorage.update_config_value(:string, "authorization", "server", server)
  end

  defp migrate_configuration(%{
    "firmware_hardware" => hw,
    "first_party_farmware" => fpf,
    "os_auto_update" => os_auto_update,
    "steps_per_mm_x" => spmx,
    "steps_per_mm_y" => spmy,
    "steps_per_mm_z" => spmz,
    "timezone" => tz, "user_env" => user_env})
  do
    import ConfigStorage, only: [update_config_value: 4]
    with {:first_party_farmware, :type_check, true}    <- {:first_party_farmware, :type_check, is_boolean(fpf)},
    {:first_party_farmware, :update_config_value, :ok} <- {:first_party_farmware, :update_config_value, update_config_value(:bool, "settings", "first_party_farmware", fpf)},
    {:firmware_hardware, :type_check, true}            <- {:firmware_hardware, :type_check, hw in ["arduino", "farmduino"]},
    {:firmware_hardware, :update_config_value, :ok}    <- {:firmware_hardware, :update_config_value, update_config_value(:string, "settings", "firmware_hardware", hw)},
    {:os_auto_update, :type_check, true}               <- {:os_auto_update, :type_check, is_boolean(os_auto_update)},
    {:os_auto_update, :update_config_value, :ok}       <- {:os_auto_update, :update_config_value, update_config_value(:bool, "settings", "os_auto_update", os_auto_update)},
    {:timezone, :type_check, true}                     <- {:timezone, :type_check, (is_binary(tz) or is_nil(tz))},
    {:timezone, :update_config_value, :ok}             <- {:timezone, :update_config_value, update_config_value(:string, "settings", "timezone", tz)},
    {:user_env, :type_check, true}                     <- {:user_env, :type_check, is_map(user_env)},
    {:user_env, :type_cast, {:ok, user_env_enc}}       <- {:user_env, :type_cast, Poison.encode(user_env)},
    {:user_env, :update_config_value, :ok}             <- {:user_env, :update_config_value, update_config_value(:string, "settings", "user_env", user_env_enc)},
    {:spmx, :update_config_value, :ok}                 <- {:spmx, :update_config_value, update_config_value(:float, "hardware_params", "movement_step_per_mm_x", (spmx || 5)  / 1)},
    {:spmy, :update_config_value, :ok}                 <- {:spmy, :update_config_value, update_config_value(:float, "hardware_params", "movement_step_per_mm_y", (spmy || 5)  / 1)},
    {:spmz, :update_config_value, :ok}                 <- {:spmz, :update_config_value, update_config_value(:float, "hardware_params", "movement_step_per_mm_z", (spmz || 25) / 1)},
    {:first_boot, :update_config_value, :ok}           <- {:first_boot, :update_config_value, update_config_value(:bool, "settings", "first_boot", false)} do
      Logger.success 1, "Configuration data from json file was merged."
      :ok
    else
      {step, sub_step, err} ->
        {:error, "#{step} failed at #{sub_step} reason: #{inspect err}"}
    end
  end

  defp migrate_hw(%{"params" => params}) do
    expected_keys = struct(Farmbot.BotState).mcu_params |> Enum.map(fn({key, _}) -> Atom.to_string(key) end)
    migrated = Map.take(params, expected_keys)
    if Enum.all?(migrated, fn({param, val}) ->
      cond do
        is_integer(val) ->
          ConfigStorage.update_config_value(:float, "hardware_params", param, (val / 1))
        is_float(val) ->
          ConfigStorage.update_config_value(:float, "hardware_params", param, val)
        is_nil(val) -> :ok
      end
    end) do
      Logger.info 1, "Migrated: #{Enum.count migrated} hardware params."
      ConfigStorage.update_config_value(:float, "hardware_params", "param_version", -5.0)
      ConfigStorage.update_config_value(:float, "hardware_params", "movement_home_spd_x", 50.0)
      ConfigStorage.update_config_value(:float, "hardware_params", "movement_home_spd_y", 50.0)
      ConfigStorage.update_config_value(:float, "hardware_params", "movement_home_spd_z", 50.0)
      :ok
    else
      {:error, "Failed to migrate params"}
    end
  end

  defp migrate_network(%{"interfaces" => ifaces}) do
    case do_migrate_network(Map.to_list(ifaces)) do
      [] -> {:error, "No networks were migrated."}
      _ -> :ok
    end
  end

  defp do_migrate_network(ifaces, acc \\ [])

  defp do_migrate_network([], acc), do: acc

  defp do_migrate_network([{ifname, %{"default" => "dhcp", "type" => "wired"}} | rest], acc) do
    %ConfigStorage.NetworkInterface{name: ifname, type: "wired", ipv4_method: "dhcp"}
    |> ConfigStorage.insert()
    |> case do
      {:ok, res} -> do_migrate_network(rest, [res | acc])
      _ -> do_migrate_network(rest, acc)
    end
  end

  defp do_migrate_network([{ifname, %{"default" => "dhcp", "type" => "wireless",
                                      "settings" => %{"key_mgmt" => "WPA-PSK", "psk" => psk, "ssid" => ssid}}} | rest], acc) do
    %ConfigStorage.NetworkInterface{
      name: ifname,
      type: "wireless",
      ssid: ssid,
      psk: psk,
      security: "WPA-PSK",
      ipv4_method: "dhcp" }
    |> ConfigStorage.insert()
    |> case do
      {:ok, res} -> do_migrate_network(rest, [res | acc])
      _ -> do_migrate_network(rest, acc)
    end
  end

  defp do_migrate_network([_ | rest], acc) do
    do_migrate_network(rest, acc)
  end
end

defmodule Farmbot.Target.ConfigMigration.BeforeNetwork do
  @moduledoc "Init module for migrating the old JSON based config."
  use GenServer
  alias Farmbot.System.ConfigStorage
  use Farmbot.Logger

  @data_path Application.get_env(:farmbot, :data_path)

  @doc false
  def start_link(_, _) do
    GenServer.start_link(__MODULE__, [], [name: __MODULE__])
  end

  def init([]) do

    old_farmware_dir = Path.join(@data_path, "farmware")
    old_config_json_file = Path.join(@data_path, "config.json")
    old_secret_file = Path.join(@data_path, "secret")

    if File.exists?(old_config_json_file) do
      File.rm_rf(old_farmware_dir)
      Logger.busy 1, "Migrating json based config."
      with :ok <- migrate_config_file(old_config_json_file),
           :ok <- migrate_secret(old_secret_file),
           _   <- migrate_fw()
      do
        Logger.success 1, "Successfully migrated."
        :ignore
      else
        {:error, step, sub_step, reason} ->
          msg = "Migration failed at step: #{step} substep: #{sub_step} reason: #{inspect reason}"
          Logger.error 1, msg
          {:stop, msg}
      end
    else
      :ignore
    end
  end

  def migrate_fw do
    hw = ConfigStorage.get_config_value(:string, "settings", "firmware_hardware")
    Farmbot.Firmware.UartHandler.Update.maybe_update_firmware(hw)
  end

  def migrate_config_file(filename) do
    with {:file_read, {:ok, file}} <- {:file_read,     File.read(filename)},
    {:json_decode,    {:ok, data}} <- {:json_decode,   Poison.decode(file)},
    {:authorization,  {:ok, auth}} <- {:authorization, Map.fetch(data, "authorization")},
    {:authorization,  :ok}         <- {:authorization, migrate_auth(auth)},
    {:configuration,  {:ok, conf}} <- {:configuration, Map.fetch(data, "configuration")},
    {:configuration,  :ok}         <- {:configuration, migrate_configuration(conf)},
    {:hardware,       {:ok, hw  }} <- {:hardware,      Map.fetch(data, "hardware")},
    {:hardware,       :ok}         <- {:hardware,      migrate_hw(hw)},
    {:network,        {:ok, net }} <- {:network,       Map.fetch(data, "network")},
    {:network,        :ok}         <- {:network,       migrate_network(net)} do
      Logger.success 1, "JSON config migrated"
      :ok
    else
      {step, {:error, reason}} -> {:error, :migrate_config_file, step, reason}
    end
  end

  def migrate_secret(filename) do
    with {:ok, bin_term} <- File.read(filename) do
      bin = :erlang.binary_to_term(bin_term)
      ConfigStorage.update_config_value(:string, "authorization", "password", bin)
      File.rm(filename)
    else
      {:error, reason} ->
        {:error, :migrate_secret_file,  :file_read, reason}
    end
  end

  defp migrate_auth(%{"server" => server}) do
    ConfigStorage.update_config_value(:string, "authorization", "server", server)
  end

  defp migrate_configuration(%{
    "firmware_hardware" => hw,
    "first_party_farmware" => fpf,
    "os_auto_update" => os_auto_update,
    "steps_per_mm_x" => spmx,
    "steps_per_mm_y" => spmy,
    "steps_per_mm_z" => spmz,
    "timezone" => tz, "user_env" => user_env})
  do
    import ConfigStorage, only: [update_config_value: 4]
    with {:first_party_farmware, :type_check, true}    <- {:first_party_farmware, :type_check, is_boolean(fpf)},
    {:first_party_farmware, :update_config_value, :ok} <- {:first_party_farmware, :update_config_value, update_config_value(:bool, "settings", "first_party_farmware", fpf)},
    {:firmware_hardware, :type_check, true}            <- {:firmware_hardware, :type_check, hw in ["arduino", "farmduino"]},
    {:firmware_hardware, :update_config_value, :ok}    <- {:firmware_hardware, :update_config_value, update_config_value(:string, "settings", "firmware_hardware", hw)},
    {:os_auto_update, :type_check, true}               <- {:os_auto_update, :type_check, is_boolean(os_auto_update)},
    {:os_auto_update, :update_config_value, :ok}       <- {:os_auto_update, :update_config_value, update_config_value(:bool, "settings", "os_auto_update", os_auto_update)},
    {:timezone, :type_check, true}                     <- {:timezone, :type_check, (is_binary(tz) or is_nil(tz))},
    {:timezone, :update_config_value, :ok}             <- {:timezone, :update_config_value, update_config_value(:string, "settings", "timezone", tz)},
    {:user_env, :type_check, true}                     <- {:user_env, :type_check, is_map(user_env)},
    {:user_env, :type_cast, {:ok, user_env_enc}}       <- {:user_env, :type_cast, Poison.encode(user_env)},
    {:user_env, :update_config_value, :ok}             <- {:user_env, :update_config_value, update_config_value(:string, "settings", "user_env", user_env_enc)},
    {:spmx, :update_config_value, :ok}                 <- {:spmx, :update_config_value, update_config_value(:float, "hardware_params", "movement_step_per_mm_x", (spmx || 5)  / 1)},
    {:spmy, :update_config_value, :ok}                 <- {:spmy, :update_config_value, update_config_value(:float, "hardware_params", "movement_step_per_mm_y", (spmy || 5)  / 1)},
    {:spmz, :update_config_value, :ok}                 <- {:spmz, :update_config_value, update_config_value(:float, "hardware_params", "movement_step_per_mm_z", (spmz || 25) / 1)},
    {:first_boot, :update_config_value, :ok}           <- {:first_boot, :update_config_value, update_config_value(:bool, "settings", "first_boot", false)} do
      Logger.success 1, "Configuration data from json file was merged."
      :ok
    else
      {step, sub_step, err} ->
        {:error, "#{step} failed at #{sub_step} reason: #{inspect err}"}
    end
  end

  defp migrate_hw(%{"params" => params}) do
    expected_keys = struct(Farmbot.BotState).mcu_params |> Enum.map(fn({key, _}) -> Atom.to_string(key) end)
    migrated = Map.take(params, expected_keys)
    if Enum.all?(migrated, fn({param, val}) ->
      cond do
        is_integer(val) ->
          ConfigStorage.update_config_value(:float, "hardware_params", param, (val / 1))
        is_float(val) ->
          ConfigStorage.update_config_value(:float, "hardware_params", param, val)
        is_nil(val) -> :ok
      end
    end) do
      if Enum.count(migrated) > 0 do
        ConfigStorage.update_config_value(:float, "hardware_params", "param_version", -5.0)
        ConfigStorage.update_config_value(:float, "hardware_params", "movement_home_spd_x", 50.0)
        ConfigStorage.update_config_value(:float, "hardware_params", "movement_home_spd_y", 50.0)
        ConfigStorage.update_config_value(:float, "hardware_params", "movement_home_spd_z", 50.0)
      end
      :ok
    else
      {:error, "Failed to migrate params"}
    end
  end

  defp migrate_network(%{"interfaces" => ifaces}) do
    case do_migrate_network(Map.to_list(ifaces)) do
      [] -> {:error, "No networks were migrated."}
      _ -> :ok
    end
  end

  defp do_migrate_network(ifaces, acc \\ [])

  defp do_migrate_network([], acc), do: acc

  defp do_migrate_network([{ifname, %{"default" => "dhcp", "type" => "wired"}} | rest], acc) do
    %ConfigStorage.NetworkInterface{name: ifname, type: "wired", ipv4_method: "dhcp"}
    |> ConfigStorage.insert()
    |> case do
      {:ok, res} -> do_migrate_network(rest, [res | acc])
      _ -> do_migrate_network(rest, acc)
    end
  end

  defp do_migrate_network([{ifname, %{"default" => "dhcp", "type" => "wireless",
                                      "settings" => %{"key_mgmt" => "WPA-PSK", "psk" => psk, "ssid" => ssid}}} | rest], acc) do
    %ConfigStorage.NetworkInterface{
      name: ifname,
      type: "wireless",
      ssid: ssid,
      psk: psk,
      security: "WPA-PSK",
      ipv4_method: "dhcp" }
    |> ConfigStorage.insert()
    |> case do
      {:ok, res} -> do_migrate_network(rest, [res | acc])
      _ -> do_migrate_network(rest, acc)
    end
  end

  defp do_migrate_network([_ | rest], acc) do
    do_migrate_network(rest, acc)
  end
end