[architect] typo

[architect] typo

defmodule Architect.Projects do
  @moduledoc """
  The Projects context.
  """

  import Ecto.Query, warn: false
  alias Architect.Repo
  alias Architect.Projects.{Repository, Project, Starter}
  alias Architect.Events

  alias Architect.Accounts.User
  use Supervisor
  require Logger

  @registry __MODULE__.Registry
  @supervisor __MODULE__.Supervisor

  def start_link(_opts \\ []) do
    Logger.debug("Starting #{Atom.to_string(__MODULE__)}")
    Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  @doc """
  Returns the list of projects.

  ## Examples

      iex> list_projects()
      [%Project{}, ...]

  """
  def list_projects() do
    Repo.all(Project)
  end

  @doc """
  Gets a single project by id.

  Raises `Ecto.NoResultsError` if the Known host does not exist.
  """
  def get_project!(id), do: Repo.get!(Project, id)

  @doc """
  Gets a single project by slug.

  Raises `Ecto.NoResultsError` if the Project does not exist.

  ## Examples

      iex> get_project_by_slug!("velocity")
      %KnownHost{}

      iex> get_project_by_slug!("Not a slug")
      ** (Ecto.NoResultsError)

  """
  def get_project_by_slug!(slug), do: Repo.get_by!(Project, slug: slug)

  @doc """
  Creates a project.

  ## Examples

      iex> create_project(%User{}, "https://github.com/velocity-ci/velocity.git")
      {:ok, %Project{}}

      iex> create_project(%User{}, "banter)
      {:error, %Ecto.Changeset{}}

  """
  def create_project(%User{} = u, address) when is_binary(address) do
    Repo.transaction(fn ->
      changeset = Project.changeset(%Project{}, %{address: address, created_by_id: u.id})

      case Repo.insert(changeset) do
        {:ok, p} ->
          Events.create_event!(u, p, %{type: :project_created})

          p

        {:error, e} ->
          Repo.rollback(e)
      end
    end)
  end

  @doc ~S"""
  Get a list of branches

  ## Examples

      iex> list_branches(project)
      [%Branch{}, ...]

  """
  def list_branches(%Project{} = project),
    do: call_repository(project, {:list_branches, []})

  @doc ~S"""
  Get a list of branches for a specific commit SHA

  ## Examples

      iex> list_branches_for_commit(project, "925fbc450c8bdb7665ec3af3129ce715927433fe")
      [%Branch{}, ...]

  """
  def list_branches_for_commit(%Project{} = project, sha) when is_binary(sha),
    do: call_repository(project, {:list_branches_for_commit, [sha]})

  @doc ~S"""
  Get a list of commits by branch

  ## Examples

      iex> list_commits(project, "master")
      [%Commit{}, ...]

  """
  def list_commits(%Project{} = project, branch) when is_binary(branch),
    do: call_repository(project, {:list_commits, [branch]})

  @doc ~S"""
  Get the default branch

  ## Examples

      iex> default_branch(project)
      %Branch{}

  """
  def default_branch(%Project{} = project),
    do: call_repository(project, {:default_branch, []})

  @doc ~S"""
  Get specific branch

  ## Examples

      iex> get_branch(project, "master")
      %Branch{}

  """
  def get_branch(%Project{} = project, branch) when is_binary(branch),
    do: call_repository(project, {:get_branch, [branch]})

  @doc ~S"""
  Get the amount of commits for the project

  ## Examples

      iex> commit_count(project)
      123

  """
  def commit_count(%Project{} = project),
    do: call_repository(project, {:commit_count, []})

  @doc ~S"""
  Get the amount of commits for the project, for a specific branch

  ## Examples

      iex> commit_count(project, "master")
      42

  """
  def commit_count(%Project{} = project, branch) when is_binary(branch),
    do: call_repository(project, {:commit_count, [branch]})

  @doc ~S"""
  List tasks

  ## Examples

      iex> list_tasks(project, {:sha, "925fbc450c8bdb7665ec3af3129ce715927433fe"})
      [%Architect.Projects.Task{}, ...]

  """
  def list_tasks(%Project{} = project, selector),
    do: call_repository(project, {:list_tasks, [selector]})

  ### Server

  @impl true
  def init(:ok) do
    :ets.new(:simple_cache, [:named_table, :public])

    children = [
      {Registry, keys: :unique, name: @registry},
      {DynamicSupervisor, name: @supervisor, strategy: :one_for_one, max_restarts: 3},
      worker(
        Starter,
        [%{registry: @registry, supervisor: @supervisor, projects: list_projects()}],
        restart: :transient
      )
    ]

    Logger.info("Running #{Atom.to_string(__MODULE__)}")

    Supervisor.init(children, strategy: :one_for_one)
  end

  @doc false
  defp call_repository(project, callback, attempt \\ 1)

  defp call_repository(_, _, attempt) when attempt > 2, do: {:error, "Failed"}

  defp call_repository(%Project{address: address, name: name} = project, {fun, args}, attempt) do
    case Registry.lookup(@registry, "#{address}-#{name}") do
      [{repository, _}] ->
        try do
          Architect.ETSCache.get(Repository, fun, [repository | args])
        catch
          kind, reason ->
            Logger.warn(
              "Failed to call repository #{address} #{name}: #{inspect(fun)} #{inspect(args)} (#{
                inspect(kind)
              }-#{inspect(reason)}), #{inspect(attempt)}..."
            )

            Process.sleep(1_000)

            call_repository(project, {fun, args}, attempt + 1)
        end

      [] ->
        Logger.warn(
          "Failed to call builder #{address} #{name} on #{inspect(@registry)}; does not exist"
        )

        call_repository(project, {fun, args}, attempt + 1)
    end
  end
end

defmodule Architect.Projects.Starter do
  use Task
  require Logger
  alias Architect.Projects.Repository

  def start_link(opts) do
    Task.start_link(__MODULE__, :run, [opts])
  end

  def run(%{projects: projects, supervisor: supervisor, registry: registry}) do
    for project <- projects do
      repository_name =
        {:via, Registry, {Architect.Projects.Registry, "#{project.address}-#{project.name}"}}

      {:ok, pid} =
        DynamicSupervisor.start_child(
          # supervisor,
          Architect.Projects.Supervisor,
          {Repository, {project.address, project.private_key, repository_name}}
        )
    end
  end
end

defmodule Architect.Projects do
  @moduledoc """
  The Projects context.
  """

  import Ecto.Query, warn: false
  alias Architect.Repo
  alias Architect.Projects.{Repository, Project, Starter}
  alias Architect.Accounts.User
  use Supervisor
  require Logger

  @registry __MODULE__.Registry
  @supervisor __MODULE__.Supervisor

  def start_link(_opts \\ []) do
    Logger.debug("Starting #{Atom.to_string(__MODULE__)}")
    Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  @doc """
  Returns the list of projects.

  ## Examples

      iex> list_projects()
      [%Project{}, ...]

  """
  def list_projects() do
    Repo.all(Project)
  end

  @doc """
  Gets a single project by id.

  Raises `Ecto.NoResultsError` if the Known host does not exist.
  """
  def get_project!(id), do: Repo.get!(Project, id)

  @doc """
  Gets a single project by slug.

  Raises `Ecto.NoResultsError` if the Project does not exist.

  ## Examples

      iex> get_project_by_slug!("velocity")
      %KnownHost{}

      iex> get_project_by_slug!("Not a slug")
      ** (Ecto.NoResultsError)

  """
  def get_project_by_slug!(slug), do: Repo.get_by!(Project, slug: slug)

  @doc """
  Creates a project.

  ## Examples

      iex> create_project(%User{}, "https://github.com/velocity-ci/velocity.git")
      {:ok, %Project{}}

      iex> create_project(%User{}, "banter)
      {:error, %Ecto.Changeset{}}

  """
  def create_project(%User{} = u, address) when is_binary(address) do
    Repo.transaction(fn ->
      changeset = Project.changeset(%Project{}, %{address: address, created_by_id: u.id})

      case Repo.insert(changeset) do
        {:ok, p} ->
          Events.create_event!(u, p, %{type: :project_created})

          p

        {:error, e} ->
          Repo.rollback(e)
      end
    end)
  end

  @doc ~S"""
  Get a list of branches

  ## Examples

      iex> list_branches(project)
      [%Branch{}, ...]

  """
  def list_branches(%Project{} = project),
    do: call_repository(project, {:list_branches, []})

  @doc ~S"""
  Get a list of branches for a specific commit SHA

  ## Examples

      iex> list_branches_for_commit(project, "925fbc450c8bdb7665ec3af3129ce715927433fe")
      [%Branch{}, ...]

  """
  def list_branches_for_commit(%Project{} = project, sha) when is_binary(sha),
    do: call_repository(project, {:list_branches_for_commit, [sha]})

  @doc ~S"""
  Get a list of commits by branch

  ## Examples

      iex> list_commits(project, "master")
      [%Commit{}, ...]

  """
  def list_commits(%Project{} = project, branch) when is_binary(branch),
    do: call_repository(project, {:list_commits, [branch]})

  @doc ~S"""
  Get the default branch

  ## Examples

      iex> default_branch(project)
      %Branch{}

  """
  def default_branch(%Project{} = project),
    do: call_repository(project, {:default_branch, []})

  @doc ~S"""
  Get specific branch

  ## Examples

      iex> get_branch(project, "master")
      %Branch{}

  """
  def get_branch(%Project{} = project, branch) when is_binary(branch),
    do: call_repository(project, {:get_branch, [branch]})

  @doc ~S"""
  Get the amount of commits for the project

  ## Examples

      iex> commit_count(project)
      123

  """
  def commit_count(%Project{} = project),
    do: call_repository(project, {:commit_count, []})

  @doc ~S"""
  Get the amount of commits for the project, for a specific branch

  ## Examples

      iex> commit_count(project, "master")
      42

  """
  def commit_count(%Project{} = project, branch) when is_binary(branch),
    do: call_repository(project, {:commit_count, [branch]})

  @doc ~S"""
  List tasks

  ## Examples

      iex> list_tasks(project, {:sha, "925fbc450c8bdb7665ec3af3129ce715927433fe"})
      [%Architect.Projects.Task{}, ...]

  """
  def list_tasks(%Project{} = project, selector),
    do: call_repository(project, {:list_tasks, [selector]})

  ### Server

  @impl true
  def init(:ok) do
    :ets.new(:simple_cache, [:named_table, :public])

    children = [
      {Registry, keys: :unique, name: @registry},
      {DynamicSupervisor, name: @supervisor, strategy: :one_for_one, max_restarts: 3},
      worker(
        Starter,
        [%{registry: @registry, supervisor: @supervisor, projects: list_projects()}],
        restart: :transient
      )
    ]

    Logger.info("Running #{Atom.to_string(__MODULE__)}")

    Supervisor.init(children, strategy: :one_for_one)
  end

  @doc false
  defp call_repository(project, callback, attempt \\ 1)

  defp call_repository(_, _, attempt) when attempt > 2, do: {:error, "Failed"}

  defp call_repository(%Project{address: address, name: name} = project, {fun, args}, attempt) do
    case Registry.lookup(@registry, "#{address}-#{name}") do
      [{repository, _}] ->
        try do
          Architect.ETSCache.get(Repository, fun, [repository | args])
        catch
          kind, reason ->
            Logger.warn(
              "Failed to call repository #{address} #{name}: #{inspect(fun)} #{inspect(args)} (#{
                inspect(kind)
              }-#{inspect(reason)}), #{inspect(attempt)}..."
            )

            Process.sleep(1_000)

            call_repository(project, {fun, args}, attempt + 1)
        end

      [] ->
        Logger.warn(
          "Failed to call builder #{address} #{name} on #{inspect(@registry)}; does not exist"
        )

        call_repository(project, {fun, args}, attempt + 1)
    end
  end
end

defmodule Architect.Projects.Starter do
  use Task
  require Logger
  alias Architect.Projects.Repository

  def start_link(opts) do
    Task.start_link(__MODULE__, :run, [opts])
  end

  def run(%{projects: projects, supervisor: supervisor, registry: registry}) do
    for project <- projects do
      repository_name =
        {:via, Registry, {Architect.Projects.Registry, "#{project.address}-#{project.name}"}}

      {:ok, pid} =
        DynamicSupervisor.start_child(
          # supervisor,
          Architect.Projects.Supervisor,
          {Repository, {project.address, project.private_key, repository_name}}
        )
    end
  end
end


All that was accomplished when fooling with datepickers

defmodule Eecrit.OldAnimalControllerTest do
  use Eecrit.ConnCase

  alias Eecrit.OldAnimal
  @valid_attrs %{date_removed_from_service: %{day: 17, month: 4, year: 2010}, kind: "some content", name: "some content", nickname: "some content", procedure_description_kind: "some content"}
  @invalid_attrs %{}

  ## Index

  @tag accessed_by: "admin"
  test "lists only currently-in-service entries by default", %{conn: conn} do
    retained = insert_old_animal(name: "retained", date_removed_from_service: nil)
    {:ok, removed_at} = Ecto.Date.cast("2012-03-05")
    deleted = insert_old_animal(name: "removed", date_removed_from_service: removed_at)
    conn = get conn, old_animal_path(conn, :index)
    assert html_response(conn, 200) =~ "All animals currently in service"
    assert Enum.find(conn.assigns.animals, &(&1.name == "retained"))
    refute Enum.find(conn.assigns.animals, &(&1.name == "removed"))
  end

  @tag accessed_by: "admin"
  test "animals with future removal dates are still shown", %{conn: conn} do
    retained = insert_old_animal(name: "retained", date_removed_from_service: nil)
    deleted = insert_old_animal(
      name: "removed",
      date_removed_from_service: Ecto.Date.cast!("2092-03-05"))
    conn = get conn, old_animal_path(conn, :index)
    assert html_response(conn, 200) =~ "All animals currently in service"
    assert Enum.find(conn.assigns.animals, &(&1.name == "retained"))
    assert Enum.find(conn.assigns.animals, &(&1.name == "removed"))
  end

  @tag accessed_by: "admin"
  test "Can be made to list out-of-service entries", %{conn: conn} do
    retained = insert_old_animal(name: "retained", date_removed_from_service: nil)

    deleted = insert_old_animal(
      name: "removed",
      date_removed_from_service: Ecto.Date.cast!("2012-03-05"))
    conn = get conn, old_animal_path(conn, :index, include_out_of_service: true)
    assert html_response(conn, 200) =~ "All animals"
    refute html_response(conn, 200) =~ "All animals currently in service"
    assert Enum.find(conn.assigns.animals, &(&1.name == "retained"))
    assert Enum.find(conn.assigns.animals, &(&1.name == "removed"))
  end

  @tag accessed_by: "admin"
  test "entries are listed in alphabetical order", %{conn: conn} do
    unordered = ~w{s d l b a m o}
    Enum.map(unordered, &(insert_old_animal(name: &1)))
    conn = get conn, old_animal_path(conn, :index)
    assert Enum.map(conn.assigns.animals, &(&1.name)) == Enum.sort(unordered)
  end

  ## NEW

  @tag accessed_by: "admin", skip: true
  test "renders form for new resources", %{conn: conn} do
    conn = get conn, old_animal_path(conn, :new)
    assert html_response(conn, 200) =~ "New animal"
  end

  @tag accessed_by: "admin", skip: true
  test "creates resource and redirects when data is valid", %{conn: conn} do
    conn = post conn, old_animal_path(conn, :create), old_animal: @valid_attrs
    assert redirected_to(conn) == old_animal_path(conn, :index)
    assert OldRepo.get_by(OldAnimal, @valid_attrs)
  end

  @tag accessed_by: "admin", skip: true
  test "does not create resource and renders errors when data is invalid", %{conn: conn} do
    conn = post conn, old_animal_path(conn, :create), old_animal: @invalid_attrs
    assert html_response(conn, 200) =~ "New old animal"
  end

  @tag accessed_by: "admin", skip: true
  test "shows chosen resource", %{conn: conn} do
    old_animal = OldRepo.insert! %OldAnimal{}
    conn = get conn, old_animal_path(conn, :show, old_animal)
    assert html_response(conn, 200) =~ "Show old animal"
  end

  @tag accessed_by: "admin", skip: true
  test "renders page not found when id is nonexistent", %{conn: conn} do
    assert_error_sent 404, fn ->
      get conn, old_animal_path(conn, :show, -1)
    end
  end

  @tag accessed_by: "admin", skip: true
  test "renders form for editing chosen resource", %{conn: conn} do
    old_animal = OldRepo.insert! %OldAnimal{}
    conn = get conn, old_animal_path(conn, :edit, old_animal)
    assert html_response(conn, 200) =~ "Edit old animal"
  end

  @tag accessed_by: "admin", skip: true
  test "updates chosen resource and redirects when data is valid", %{conn: conn} do
    old_animal = OldRepo.insert! %OldAnimal{}
    conn = put conn, old_animal_path(conn, :update, old_animal), old_animal: @valid_attrs
    assert redirected_to(conn) == old_animal_path(conn, :show, old_animal)
    assert OldRepo.get_by(OldAnimal, @valid_attrs)
  end

  @tag accessed_by: "admin", skip: true
  test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
    old_animal = OldRepo.insert! %OldAnimal{}
    conn = put conn, old_animal_path(conn, :update, old_animal), old_animal: @invalid_attrs
    assert html_response(conn, 200) =~ "Edit old animal"
  end
end

defmodule Eecrit.OldAnimalControllerTest do
  use Eecrit.ConnCase

  alias Eecrit.OldAnimal
  @valid_attrs %{date_removed_from_service: %{day: 17, month: 4, year: 2010}, kind: "some content", name: "some content", nickname: "some content", procedure_description_kind: "some content"}
  @invalid_attrs %{}

  ## Index

  @tag accessed_by: "admin"
  test "lists only currently-in-service entries by default", %{conn: conn} do
    retained = insert_old_animal(name: "retained", date_removed_from_service: nil)
    {:ok, removed_at} = Ecto.Date.cast("2012-03-05")
    deleted = insert_old_animal(name: "removed", date_removed_from_service: removed_at)
    conn = get conn, old_animal_path(conn, :index)
    assert html_response(conn, 200) =~ "All animals currently in service"
    assert Enum.find(conn.assigns.animals, &(&1.name == "retained"))
    refute Enum.find(conn.assigns.animals, &(&1.name == "removed"))
  end

  @tag accessed_by: "admin"
  test "animals with future removal dates are still shown", %{conn: conn} do
    retained = insert_old_animal(name: "retained", date_removed_from_service: nil)
    deleted = insert_old_animal(
      name: "removed",
      date_removed_from_service: Ecto.Date.cast!("2092-03-05"))
    conn = get conn, old_animal_path(conn, :index)
    assert html_response(conn, 200) =~ "All animals currently in service"
    assert Enum.find(conn.assigns.animals, &(&1.name == "retained"))
    assert Enum.find(conn.assigns.animals, &(&1.name == "removed"))
  end

  @tag accessed_by: "admin"
  test "Can be made to list out-of-service entries", %{conn: conn} do
    retained = insert_old_animal(name: "retained", date_removed_from_service: nil)

    deleted = insert_old_animal(
      name: "removed",
      date_removed_from_service: Ecto.Date.cast!("2012-03-05"))
    conn = get conn, old_animal_path(conn, :index, include_out_of_service: true)
    assert html_response(conn, 200) =~ "All animals"
    refute html_response(conn, 200) =~ "All animals currently in service"
    assert Enum.find(conn.assigns.animals, &(&1.name == "retained"))
    assert Enum.find(conn.assigns.animals, &(&1.name == "removed"))
  end

  @tag accessed_by: "admin"
  test "entries are listed in alphabetical order", %{conn: conn} do
    unordered = ~w{s d l b a m o}
    Enum.map(unordered, &(insert_old_animal(name: &1)))
    conn = get conn, old_animal_path(conn, :index)
    assert Enum.map(conn.assigns.animals, &(&1.name)) == Enum.sort(unordered)
  end

  @tag accessed_by: "admin", skip: true
  test "renders form for new resources", %{conn: conn} do
    conn = get conn, old_animal_path(conn, :new)
    assert html_response(conn, 200) =~ "New old animal"
  end

  @tag accessed_by: "admin", skip: true
  test "creates resource and redirects when data is valid", %{conn: conn} do
    conn = post conn, old_animal_path(conn, :create), old_animal: @valid_attrs
    assert redirected_to(conn) == old_animal_path(conn, :index)
    assert OldRepo.get_by(OldAnimal, @valid_attrs)
  end

  @tag accessed_by: "admin", skip: true
  test "does not create resource and renders errors when data is invalid", %{conn: conn} do
    conn = post conn, old_animal_path(conn, :create), old_animal: @invalid_attrs
    assert html_response(conn, 200) =~ "New old animal"
  end

  @tag accessed_by: "admin", skip: true
  test "shows chosen resource", %{conn: conn} do
    old_animal = OldRepo.insert! %OldAnimal{}
    conn = get conn, old_animal_path(conn, :show, old_animal)
    assert html_response(conn, 200) =~ "Show old animal"
  end

  @tag accessed_by: "admin", skip: true
  test "renders page not found when id is nonexistent", %{conn: conn} do
    assert_error_sent 404, fn ->
      get conn, old_animal_path(conn, :show, -1)
    end
  end

  @tag accessed_by: "admin", skip: true
  test "renders form for editing chosen resource", %{conn: conn} do
    old_animal = OldRepo.insert! %OldAnimal{}
    conn = get conn, old_animal_path(conn, :edit, old_animal)
    assert html_response(conn, 200) =~ "Edit old animal"
  end

  @tag accessed_by: "admin", skip: true
  test "updates chosen resource and redirects when data is valid", %{conn: conn} do
    old_animal = OldRepo.insert! %OldAnimal{}
    conn = put conn, old_animal_path(conn, :update, old_animal), old_animal: @valid_attrs
    assert redirected_to(conn) == old_animal_path(conn, :show, old_animal)
    assert OldRepo.get_by(OldAnimal, @valid_attrs)
  end

  @tag accessed_by: "admin", skip: true
  test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
    old_animal = OldRepo.insert! %OldAnimal{}
    conn = put conn, old_animal_path(conn, :update, old_animal), old_animal: @invalid_attrs
    assert html_response(conn, 200) =~ "Edit old animal"
  end
end


Parse the response to RECORD_DELETE operations

defmodule MarcoPolo.Protocol do
  require Integer
  import MarcoPolo.Protocol.BinaryHelpers
  alias MarcoPolo.Error
  alias MarcoPolo.Protocol.RecordSerialization

  @type sid :: non_neg_integer
  @type op_code :: non_neg_integer
  @type op_name :: atom

  @ok    <<0>>
  @error <<1>>
  @null  <<-1 :: int>>

  @doc """
  """
  @spec encode_op(op_name, [term]) :: iodata
  def encode_op(op_name, args \\ []) do
    [req_code(op_name), Enum.map(args, &serialize/1)]
  end

  @doc """
  Serializes an Elixir term to an OrientDB term according to the binary
  protocol.

  Supported Elixir terms are:

    * booleans (`true` and `false`)
    * `nil`
    * binaries (hence strings)
    * integers (by default encoded as "int", but the size of the integer can be
      specified by "tagging" the integer, e.g., `{:short, 28}` or
      `{:long, 1000}`).

  """
  @spec serialize(term) :: iodata
  def serialize(term)

  # Booleans.
  def serialize(true),  do: <<1>>
  def serialize(false), do: <<0>>

  # nil.
  def serialize(nil), do: serialize({:int, -1})

  # Strings and bytes.
  def serialize(str) when is_binary(str), do: serialize({:int, byte_size(str)}) <> str

  # Encoding an Elixir integer defaults to encoding an OrientDB int (4 bytes).
  def serialize(i) when is_integer(i), do: serialize({:int, i})

  # Typed integers (short, int and long) have to be tagged.
  def serialize({:short, i}), do: <<i :: short>>
  def serialize({:int, i}),   do: <<i :: int>>
  def serialize({:long, i}),  do: <<i :: long>>

  @doc """
  """
  @spec parse_connection_header(binary) :: {:ok, sid, binary} | Error.t
  def parse_connection_header(@ok <> @null <> <<sid :: int, rest :: binary>>),
    do: {:ok, sid, rest}
  def parse_connection_header(@error <> @null <> rest),
    do: %Error{message: "error (binary dump: #{inspect rest})"}

  @doc """
  """
  @spec parse_header(binary, boolean) ::
    {:ok, sid, binary, binary}
    | {:ok, sid, binary}
    | Error.t
  def parse_header(data, token?)

  def parse_header(@ok <> <<sid :: int, rest :: binary>>, true = _token?) do
    {token, rest} = parse(rest, :bytes)
    {:ok, sid, token, rest}
  end

  def parse_header(@ok <> <<sid :: int, rest :: binary>>, false = _token?) do
    {:ok, sid, rest}
  end

  def parse_header(@error <> <<_sid :: int, rest :: binary>>, token?) do
    if token? do
      {_token, rest} = parse(rest, :bytes)
    end

    %Error{message: "error (binary dump: #{inspect rest})"}
  end

  @doc """
  """
  @spec parse_resp(op_name, binary, boolean) ::
    {:ok, sid, binary, binary}
    | {:ok, sid, binary}
    | Error.t
  def parse_resp(op_name, data, token?) do
    case parse_header(data, token?) do
      {:ok, sid, rest}        -> {:ok, sid, parse_resp_contents(op_name, rest)}
      {:ok, sid, token, rest} -> {:ok, sid, token, parse_resp_contents(op_name, rest)}
      %Error{} = error        -> error
    end
  end

  @doc """
  """
  @spec parse(binary, atom) :: {binary, binary}
  def parse(<<length :: int, data :: binary>>, :bytes) do
    length = bytes(length)
    <<parsed :: bits-size(length), rest :: binary>> = data
    {parsed, rest}
  end

  def parse(data, :string) do
    parse(data, :bytes)
  end

  defp parse_resp_contents(:db_create, <<>>) do
    []
  end

  defp parse_resp_contents(:db_exist, <<exists>>) do
    [exists == 1]
  end

  defp parse_resp_contents(:db_drop, <<>>) do
    []
  end

  defp parse_resp_contents(:db_size, <<size :: long>>) do
    [size]
  end

  defp parse_resp_contents(:db_countrecords, <<count :: long>>) do
    [count]
  end

  defp parse_resp_contents(:db_reload, <<num_of_clusters :: short, rest :: binary>>) do
    Enum.map_reduce 1..num_of_clusters, rest, fn _, acc ->
      {cluster_name, acc} = parse(acc, :string)
      <<cluster_id :: short, acc :: binary>> = acc
      {{cluster_name, cluster_id}, acc}
    end
  end

  defp parse_resp_contents(:record_load, data) do
    parse_resp_contents(:record_load, data, []) |> Enum.reverse
  end

  defp parse_resp_contents(:record_load, <<1, type, version :: int, rest :: binary>>, acc) do
    {record_content, rest} = parse(rest, :bytes)
    record = RecordSerialization.decode(record_content)
    parse_resp_contents(:record_load, rest, [{type, version, record}|acc])
  end

  defp parse_resp_contents(:record_load, <<0>>, acc) do
    acc
  end

  defp parse_resp_contents(:record_delete, <<1>>), do: true
  defp parse_resp_contents(:record_delete, <<0>>), do: false

  defp req_code(:shutdown),                        do: 1
  defp req_code(:connect),                         do: 2
  defp req_code(:db_open),                         do: 3
  defp req_code(:db_create),                       do: 4
  defp req_code(:db_exist),                        do: 6
  defp req_code(:db_drop),                         do: 7
  defp req_code(:config_get),                      do: 70
  defp req_code(:config_set),                      do: 71
  defp req_code(:config_list),                     do: 72
  defp req_code(:db_list),                         do: 74
  defp req_code(:db_close),                        do: 5
  defp req_code(:db_size),                         do: 8
  defp req_code(:db_countrecords),                 do: 9
  defp req_code(:datacluster_copy),                do: 14
  defp req_code(:datacluster_lh_cluster_is_used),  do: 16
  defp req_code(:record_metadata),                 do: 29
  defp req_code(:record_load),                     do: 30
  defp req_code(:record_create),                   do: 31
  defp req_code(:record_update),                   do: 32
  defp req_code(:record_delete),                   do: 33
  defp req_code(:record_copy),                     do: 34
  defp req_code(:record_clean_out),                do: 38
  defp req_code(:positions_floor),                 do: 39
  defp req_code(:command),                         do: 41
  defp req_code(:positions_ceiling),               do: 42
  defp req_code(:tx_commit),                       do: 60
  defp req_code(:db_reload),                       do: 73
  defp req_code(:push_record),                     do: 79
  defp req_code(:push_distrib_config),             do: 80
  defp req_code(:db_copy),                         do: 90
  defp req_code(:replication),                     do: 91
  defp req_code(:cluster),                         do: 92
  defp req_code(:db_transfer),                     do: 93
  defp req_code(:db_freeze),                       do: 94
  defp req_code(:db_release),                      do: 95
  defp req_code(:create_sbtree_bonsai),            do: 110
  defp req_code(:sbtree_bonsai_get),               do: 111
  defp req_code(:sbtree_bonsai_first_key),         do: 112
  defp req_code(:sbtree_bonsai_get_entries_major), do: 113
  defp req_code(:ridbag_get_size),                 do: 114
end

defmodule MarcoPolo.Protocol do
  require Integer
  import MarcoPolo.Protocol.BinaryHelpers
  alias MarcoPolo.Error
  alias MarcoPolo.Protocol.RecordSerialization

  @type sid :: non_neg_integer
  @type op_code :: non_neg_integer
  @type op_name :: atom

  @ok    <<0>>
  @error <<1>>
  @null  <<-1 :: int>>

  @doc """
  """
  @spec encode_op(op_name, [term]) :: iodata
  def encode_op(op_name, args \\ []) do
    [req_code(op_name), Enum.map(args, &serialize/1)]
  end

  @doc """
  Serializes an Elixir term to an OrientDB term according to the binary
  protocol.

  Supported Elixir terms are:

    * booleans (`true` and `false`)
    * `nil`
    * binaries (hence strings)
    * integers (by default encoded as "int", but the size of the integer can be
      specified by "tagging" the integer, e.g., `{:short, 28}` or
      `{:long, 1000}`).

  """
  @spec serialize(term) :: iodata
  def serialize(term)

  # Booleans.
  def serialize(true),  do: <<1>>
  def serialize(false), do: <<0>>

  # nil.
  def serialize(nil), do: serialize({:int, -1})

  # Strings and bytes.
  def serialize(str) when is_binary(str), do: serialize({:int, byte_size(str)}) <> str

  # Encoding an Elixir integer defaults to encoding an OrientDB int (4 bytes).
  def serialize(i) when is_integer(i), do: serialize({:int, i})

  # Typed integers (short, int and long) have to be tagged.
  def serialize({:short, i}), do: <<i :: short>>
  def serialize({:int, i}),   do: <<i :: int>>
  def serialize({:long, i}),  do: <<i :: long>>

  @doc """
  """
  @spec parse_connection_header(binary) :: {:ok, sid, binary} | Error.t
  def parse_connection_header(@ok <> @null <> <<sid :: int, rest :: binary>>),
    do: {:ok, sid, rest}
  def parse_connection_header(@error <> @null <> rest),
    do: %Error{message: "error (binary dump: #{inspect rest})"}

  @doc """
  """
  @spec parse_header(binary, boolean) ::
    {:ok, sid, binary, binary}
    | {:ok, sid, binary}
    | Error.t
  def parse_header(data, token?)

  def parse_header(@ok <> <<sid :: int, rest :: binary>>, true = _token?) do
    {token, rest} = parse(rest, :bytes)
    {:ok, sid, token, rest}
  end

  def parse_header(@ok <> <<sid :: int, rest :: binary>>, false = _token?) do
    {:ok, sid, rest}
  end

  def parse_header(@error <> <<_sid :: int, rest :: binary>>, token?) do
    if token? do
      {_token, rest} = parse(rest, :bytes)
    end

    %Error{message: "error (binary dump: #{inspect rest})"}
  end

  @doc """
  """
  @spec parse_resp(op_name, binary, boolean) ::
    {:ok, sid, binary, binary}
    | {:ok, sid, binary}
    | Error.t
  def parse_resp(op_name, data, token?) do
    case parse_header(data, token?) do
      {:ok, sid, rest}        -> {:ok, sid, parse_resp_contents(op_name, rest)}
      {:ok, sid, token, rest} -> {:ok, sid, token, parse_resp_contents(op_name, rest)}
      %Error{} = error        -> error
    end
  end

  @doc """
  """
  @spec parse(binary, atom) :: {binary, binary}
  def parse(<<length :: int, data :: binary>>, :bytes) do
    length = bytes(length)
    <<parsed :: bits-size(length), rest :: binary>> = data
    {parsed, rest}
  end

  def parse(data, :string) do
    parse(data, :bytes)
  end

  defp parse_resp_contents(:db_create, <<>>) do
    []
  end

  defp parse_resp_contents(:db_exist, <<exists>>) do
    [exists == 1]
  end

  defp parse_resp_contents(:db_drop, <<>>) do
    []
  end

  defp parse_resp_contents(:db_size, <<size :: long>>) do
    [size]
  end

  defp parse_resp_contents(:db_countrecords, <<count :: long>>) do
    [count]
  end

  defp parse_resp_contents(:db_reload, <<num_of_clusters :: short, rest :: binary>>) do
    Enum.map_reduce 1..num_of_clusters, rest, fn _, acc ->
      {cluster_name, acc} = parse(acc, :string)
      <<cluster_id :: short, acc :: binary>> = acc
      {{cluster_name, cluster_id}, acc}
    end
  end

  defp parse_resp_contents(:record_load, data) do
    parse_resp_contents(:record_load, data, []) |> Enum.reverse
  end

  defp parse_resp_contents(:record_load, <<1, type, version :: int, rest :: binary>>, acc) do
    {record_content, rest} = parse(rest, :bytes)
    record = RecordSerialization.decode(record_content)
    parse_resp_contents(:record_load, rest, [{type, version, record}|acc])
  end

  defp parse_resp_contents(:record_load, <<0>>, acc) do
    acc
  end

  defp req_code(:shutdown),                        do: 1
  defp req_code(:connect),                         do: 2
  defp req_code(:db_open),                         do: 3
  defp req_code(:db_create),                       do: 4
  defp req_code(:db_exist),                        do: 6
  defp req_code(:db_drop),                         do: 7
  defp req_code(:config_get),                      do: 70
  defp req_code(:config_set),                      do: 71
  defp req_code(:config_list),                     do: 72
  defp req_code(:db_list),                         do: 74
  defp req_code(:db_close),                        do: 5
  defp req_code(:db_size),                         do: 8
  defp req_code(:db_countrecords),                 do: 9
  defp req_code(:datacluster_copy),                do: 14
  defp req_code(:datacluster_lh_cluster_is_used),  do: 16
  defp req_code(:record_metadata),                 do: 29
  defp req_code(:record_load),                     do: 30
  defp req_code(:record_create),                   do: 31
  defp req_code(:record_update),                   do: 32
  defp req_code(:record_delete),                   do: 33
  defp req_code(:record_copy),                     do: 34
  defp req_code(:record_clean_out),                do: 38
  defp req_code(:positions_floor),                 do: 39
  defp req_code(:command),                         do: 41
  defp req_code(:positions_ceiling),               do: 42
  defp req_code(:tx_commit),                       do: 60
  defp req_code(:db_reload),                       do: 73
  defp req_code(:push_record),                     do: 79
  defp req_code(:push_distrib_config),             do: 80
  defp req_code(:db_copy),                         do: 90
  defp req_code(:replication),                     do: 91
  defp req_code(:cluster),                         do: 92
  defp req_code(:db_transfer),                     do: 93
  defp req_code(:db_freeze),                       do: 94
  defp req_code(:db_release),                      do: 95
  defp req_code(:create_sbtree_bonsai),            do: 110
  defp req_code(:sbtree_bonsai_get),               do: 111
  defp req_code(:sbtree_bonsai_first_key),         do: 112
  defp req_code(:sbtree_bonsai_get_entries_major), do: 113
  defp req_code(:ridbag_get_size),                 do: 114
end


better parser

better parser

defmodule Pan.Parser.Analyzer do
  alias Pan.Parser.Iterator
  alias Pan.Parser.Helpers
  defdelegate dm(left, right), to: Pan.Parser.Helpers, as: :deep_merge

#wrappers to dive into
  def call(map, "tag", [:rss,     _, value]), do: Iterator.parse(map, "tag", value)
  def call(map, "tag", [:channel, _, value]), do: Iterator.parse(map, "tag", value)

# simple tags to include in podcast
  def call(_, "tag", [:title,             _, []]), do: %{}
  def call(_, "tag", [:title,             _, [value]]), do: %{title: value}
  def call(_, "tag", [:"itunes:author",   _, []]), do: %{}
  def call(_, "tag", [:author,            _, [value]]), do: %{author: value}
  def call(_, "tag", [:"itunes:author",   _, [value]]), do: %{author: value}
  def call(_, "tag", [:"itunes:summary",  _, []]),      do: %{}
  def call(_, "tag", [:"itunes:summary",  _, [value]]), do: %{summary: value}
  def call(_, "tag", [:link,              _, [value]]), do: %{website: value}
  def call(_, "tag", [:"itunes:explicit", _, [value]]), do: %{explicit: Helpers.boolify(value)}
  def call(_, "tag", [:lastBuildDate,     _, [value]]) do
    %{last_build_date: Helpers.to_ecto_datetime(value)}
  end

# image with fallback to itunes:image
  def call(map, "tag", [:image, _, value]), do: Iterator.parse(map, "image", value)
  def call(_, "image", [:title, _, [value]]), do: %{image_title: value}
  def call(_, "image", [:url,   _, [value]]), do: %{image_url: value}
  def call(map, "image", [:link,        _, _]), do: map
  def call(map, "image", [:description, _, _]), do: map
  def call(map, "image", [:width,       _, _]), do: map
  def call(map, "image", [:height,      _, _]), do: map

  def call(map, "tag", [:"itunes:image", attr, _]) do
    if map[:image_url], do: map,
                        else: %{image_url: attr[:href],
                                image_title: attr[:href]}
  end

  def call(map, "tag", [:"itunes:image", _, [value]]) do
    if map[:image_url], do: map,
                        else: %{image_url: value}
  end

# Description with fallback to itunes:subtitle
  def call(_, "tag", [:description, _, []]), do: %{}
  def call(_, "tag", [:description, _, [value]]), do: %{description: value}

  def call(_, "tag", [:"itunes:subtitle", _, []]), do: %{}
  def call(map, "tag", [:"itunes:subtitle", _, [value]]) do
    if map[:description], do: %{},
                          else: %{description: value}
  end

# simple tags to include into nested structure
  def call(_, "tag", [:generator, _, [value]]), do: %{feed: %{ feed_generator: value}}

# the links are a mixture of the two above
  def call(_, "tag", [:"atom:link", attr, _]) do
    case attr[:rel] do
      "self"      -> %{feed: %{ self_link_title: attr[:title],
                            self_link_url: attr[:href]}}
      "next"      -> %{feed: %{ next_page_url: attr[:href]}}
      "prev"      -> %{feed: %{ prev_page_url: attr[:href]}}
      "previous"  -> %{feed: %{ prev_page_url: attr[:href]}}
      "first"     -> %{feed: %{ first_page_url: attr[:href]}}
      "last"      -> %{feed: %{ last_page_url: attr[:href]}}
      "hub"       -> %{feed: %{ hub_link_url: attr[:href]}}
      "search"    -> %{}
      "related"   -> %{}
      "alternate" ->
        uuid = String.to_atom(UUID.uuid1())
        alternate_feed_map = %{uuid => %{title: attr[:title], url: attr[:href]}}
        %{feed: %{alternate_feeds: alternate_feed_map}}
      "payment" -> %{payment_link_title: attr[:title],
                     payment_link_url: String.slice(attr[:href], 0, 255)}
    end
  end

  def call(_, "tag", [:"rawvoice:donate", attr, [value]]), do: %{payment_link_title: value,
                                                                 payment_link_url: attr[:href]}

  def call(_, "tag", [:"atom10:link", attr, _]) do
    case attr[:rel] do
      "self"  -> %{feed: %{ self_link_title: attr[:title],
                            self_link_url: attr[:href]}}
      "hub" -> %{}
    end
  end

# tags to ignore
  def call(map, "tag", [tag_atom, _, _]) when tag_atom in [
    :"feedpress:locale", :"fyyd:verify", :"itunes:block", :"itunes:keywords", :"media:thumbnail",
    :"media:keywords", :"media:category", :category, :site, :docs, :"feedburner:info", :logo,
    :"media:credit", :"media:copyright", :"media:rating", :"media:description", :"copyright",
    :"feedburner:feedFlare", :"geo:lat", :"geo:long", :"creativeCommons:license",
    :"feedburner:emailServiceId", :"feedburner:feedburnerHostname", :managingEditor, :pubDate,
    :"sy:updatePeriod", :"sy:updateFrequency", :"wfw:commentRss", :"rawvoice:subscribe",
    :webMaster, :ttl, :"itunes:new-feed-url", :"googleplay:description", :"googleplay:email",
    :"googleplay:category", :"rawvoice:rating", :"rawvoice:location", :"rawvoice:frequency",
    :"ppg:seriesDetails", :"ppg:systemRef", :"ppg:network", :cloud, :"googleplay:image",
    :"googleplay:author", :"googleplay:explicit", :feed, :webmaster, :ilink, :ffmpeg, :domain,
    :lame, :broadcastlimit, :"itunes:link", :"channelExportDir", :"atom:id",
    :"openSearch:totalResults", :"openSearch:startIndex", :"openSearch:itemsPerPage", :"html",
    :"managingeditor", :"ard:programInformation", :"dc:creator", :"itunes:complete", :feedType,
    :changefreq, :"dc:title"
  ], do: map

  def call(_, "episode", [tag_atom, _, _]) when tag_atom in [
    :"googleplay:description", :"googleplay:image", :"googleplay:explicit", :"googleplay:block",
    :"frn:id", :"frn:title", :"frn:language", :"frn:art", :"frn:radio", :"frn:serie", :"frn:laenge",
    :"frn:licence", :"frn:last_update", :"itunes:keywords", :"post-id", :author, :"itunes:explicit",
    :category, :"dc:creator", :comments, :"media:content", :"feedburner:origLink", :"itunes:image",
    :"feedburner:origEnclosureLink", :"wfw:commentRss", :"slash:comments", :"itunes:block",
    :"itunes:order", :"ppg:canonical", :"cba:productionDate", :"cba:broadcastDate", :payment,
    :"cba:containsCopyright", :"media:thumbnail", :image, :source, :"media:description", :programid,
    :poddid, :"dcterms:modified", :"dcterms:created", :toPubDate, :audioId, :"atom:updated",
    :"thr:total", :"ard:visibility", :"series:name", :"rawvoice:poster", :"georss:point",
    :"copyright", :"ard:programInformation", :"sc:chapters", :"xhtml:body"
  ], do: %{}

# We expect several language tags
  def call(_, "tag", [:language, _, [value]]) do
    uuid = String.to_atom(UUID.uuid1())
    %{languages: %{uuid => %{shortcode: value}}}
  end

# We expect several contributors
  def call(map, "tag", [:"atom:contributor", _, value]) do
    Iterator.parse(map, "contributor", value, UUID.uuid1())
  end

  def call("contributor", [:"atom:name", _, [value]]), do: %{name: value}
  def call("contributor", [:"atom:uri",  _, [value]]), do: %{uri: value}

# We expect one owner
  def call(map, "tag", [:"itunes:owner", _, value]), do: Iterator.parse(map, "owner", value)
  def call(_, "owner", [:"itunes:name",   _, []]), do: %{}
  def call(_, "owner", [:"itunes:name",   _, [value]]), do: %{name: value}
  def call(_, "owner", [:"itunes:email",  _, []]), do: %{}
  def call(_, "owner", [:"itunes:email",  _, [value]]), do: %{email: value}

# Parsing categories infintely deep
  def call(_, "tag", [:"itunes:category", attr, []]) do
    {:ok, category} = Pan.Parser.Category.find_or_create(attr[:text], nil)
    %{categories: %{category.id => true}}
  end
  def call(_, "tag", [:"itunes:category", [], [value]]) do
    {:ok, category} = Pan.Parser.Category.find_or_create(value, nil)
    %{categories: %{category.id => true}}
  end
  def call(_, "tag", [:"itunes:category", attr, value]) do
    {:ok, category} = Pan.Parser.Category.find_or_create(attr[:text], nil)
    Iterator.parse(%{categories: %{category.id => true}}, "category", value, category.id)
  end

  def call("category", [:"itunes:category", attr, []], parent_id) do
    {:ok, category} = Pan.Parser.Category.find_or_create(attr[:text], parent_id)
    %{categories: %{category.id => true}}
  end

  def call("category", [:"itunes:category", attr, value], parent_id) do
    {:ok, category} = Pan.Parser.Category.find_or_create(attr[:text], parent_id)
    Iterator.parse(%{categories: %{category.id => true}}, "category", value, category.id)
  end

# Episodes
  def call(map, "tag", [:item, _, value]), do: Iterator.parse(map, "episode", value, UUID.uuid1())

  def call(_, "episode", [:title,             _, [value]]), do: %{title:       String.slice(value, 0, 255)}
  def call(_, "episode", [:link,              _, [value]]), do: %{link:        String.slice(value, 0, 255)}
  def call(_, "episode", [:guid,              _, [value]]), do: %{guid:        String.slice(value, 0, 255)}
  def call(_, "episode", [:guid,              _, _]), do: %{}
  def call(_, "episode", [:description,       _, []]), do: %{}
  def call(_, "episode", [:description,       _, [value | _]]), do: %{description: HtmlSanitizeEx2.basic_html_reduced(value)}
  def call(_, "episode", [:"content:encoded", _, []]), do: %{}
  def call(_, "episode", [:"content:encoded", _, [value]]), do: %{shownotes: HtmlSanitizeEx2.basic_html_reduced(value)}
  def call(_, "episode", [:"itunes:summary",  _, []]), do: %{}
  def call(_, "episode", [:"itunes:summary",  _, [value | _]]), do: %{summary: HtmlSanitizeEx2.basic_html_reduced(value)}
  def call(_, "episode", [:"itunes:subtitle", _, []]), do: %{}
  def call(_, "episode", [:"itunes:subtitle", _, [value]]), do: %{subtitle: String.slice(value, 0, 255)}
  def call(_, "episode", [:"itunes:author",   _, []]), do: %{}
  def call(_, "episode", [:"itunes:author",   _, [value]]), do: %{author: String.slice(value, 0, 255)}
  def call(_, "episode", [:"itunes:duration", _, []]), do: %{}
  def call(_, "episode", [:"itunes:duration", _, [value]]), do: %{duration: value}

  def call(_, "episode", [:pubDate,           _, [value]]) do
    %{publishing_date: Helpers.to_ecto_datetime(value)}
  end

  def call(_, "episode", [:"atom:link", attr, _]) do
    case attr[:rel] do
      "http://podlove.org/deep-link" -> %{deep_link: String.slice(attr[:href], 0, 255)}
      "payment" ->                      %{payment_link_title: attr[:title],
                                          payment_link_url: String.slice(attr[:href], 0, 255)}
    end
  end

# Enclosures a.k.a. Audiofiles
  def call(_, "episode", [:enclosure, attr, _]) do
    enclosure_map = %{url:    String.slice(attr[:url], 0, 255),
                      length: String.slice(attr[:length], 0, 255),
                      type:   String.slice(attr[:type], 0, 255),
                      guid:   String.slice(attr[:"bitlove:guid"], 0, 255)}
    uuid = String.to_atom(UUID.uuid1())
    %{enclosures: %{uuid => enclosure_map}}
  end

# Chapters
  def call(_, "episode", [:"psc:chapters", _, value]) do
    Iterator.parse(%{}, "chapter", value)
  end

  def call("chapter", [:"psc:chapter", attr, _]) do
    chapter_uuid = String.to_atom(UUID.uuid1())
    %{chapter_uuid => %{start: attr[:start], title: String.slice(attr[:title], 0, 255)}}
  end

# Episode contributors
  def call(_, "episode", [:"atom:contributor", _, value]) do
    contributor_uuid = String.to_atom(UUID.uuid1())
    Iterator.parse(%{contributors: %{contributor_uuid => %{}}}, "episode-contributor", value, contributor_uuid)
  end

  def call("episode-contributor", [:"atom:name", _ , [value]]), do: %{name: value}
  def call("episode-contributor", [:"atom:uri",  _ , [value]]), do: %{uri: value}

# Show debugging information for unknown tags on console
  def call(_, mode, [tag, attr, value]) do
    IO.puts "\n\e[96m === Tag unknown: ==="
    IO.puts "Mode: " <> mode
    IO.puts "Tag: " <> ~s/:"/ <> to_string(tag) <> ~s/"/
    IO.puts "Attr: "
    IO.inspect attr
    IO.puts "Value: "
    IO.inspect value
    IO.puts " =================\e[0m"
    raise "Tag unknown"
  end
end

defmodule Pan.Parser.Analyzer do
  alias Pan.Parser.Iterator
  alias Pan.Parser.Helpers
  defdelegate dm(left, right), to: Pan.Parser.Helpers, as: :deep_merge

#wrappers to dive into
  def call(map, "tag", [:rss,     _, value]), do: Iterator.parse(map, "tag", value)
  def call(map, "tag", [:channel, _, value]), do: Iterator.parse(map, "tag", value)

# simple tags to include in podcast
  def call(_, "tag", [:title,             _, []]), do: %{}
  def call(_, "tag", [:title,             _, [value]]), do: %{title: value}
  def call(_, "tag", [:"itunes:author",   _, []]), do: %{}
  def call(_, "tag", [:"itunes:author",   _, [value]]), do: %{author: value}
  def call(_, "tag", [:"itunes:summary",  _, []]),      do: %{}
  def call(_, "tag", [:"itunes:summary",  _, [value]]), do: %{summary: value}
  def call(_, "tag", [:link,              _, [value]]), do: %{website: value}
  def call(_, "tag", [:"itunes:explicit", _, [value]]), do: %{explicit: Helpers.boolify(value)}
  def call(_, "tag", [:lastBuildDate,     _, [value]]) do
    %{last_build_date: Helpers.to_ecto_datetime(value)}
  end

# image with fallback to itunes:image
  def call(map, "tag", [:image, _, value]), do: Iterator.parse(map, "image", value)
  def call(_, "image", [:title, _, [value]]), do: %{image_title: value}
  def call(_, "image", [:url,   _, [value]]), do: %{image_url: value}
  def call(map, "image", [:link,        _, _]), do: map
  def call(map, "image", [:description, _, _]), do: map
  def call(map, "image", [:width,       _, _]), do: map
  def call(map, "image", [:height,      _, _]), do: map

  def call(map, "tag", [:"itunes:image", attr, _]) do
    if map[:image_url], do: map,
                        else: %{image_url: attr[:href],
                                image_title: attr[:href]}
  end

  def call(map, "tag", [:"itunes:image", _, [value]]) do
    if map[:image_url], do: map,
                        else: %{image_url: value}
  end

# Description with fallback to itunes:subtitle
  def call(_, "tag", [:description, _, []]), do: %{}
  def call(_, "tag", [:description, _, [value]]), do: %{description: value}

  def call(_, "tag", [:"itunes:subtitle", _, []]), do: %{}
  def call(map, "tag", [:"itunes:subtitle", _, [value]]) do
    if map[:description], do: %{},
                          else: %{description: value}
  end

# simple tags to include into nested structure
  def call(_, "tag", [:generator, _, [value]]), do: %{feed: %{ feed_generator: value}}

# the links are a mixture of the two above
  def call(_, "tag", [:"atom:link", attr, _]) do
    case attr[:rel] do
      "self"      -> %{feed: %{ self_link_title: attr[:title],
                            self_link_url: attr[:href]}}
      "next"      -> %{feed: %{ next_page_url: attr[:href]}}
      "prev"      -> %{feed: %{ prev_page_url: attr[:href]}}
      "previous"  -> %{feed: %{ prev_page_url: attr[:href]}}
      "first"     -> %{feed: %{ first_page_url: attr[:href]}}
      "last"      -> %{feed: %{ last_page_url: attr[:href]}}
      "hub"       -> %{feed: %{ hub_link_url: attr[:href]}}
      "search"    -> %{}
      "related"   -> %{}
      "alternate" ->
        uuid = String.to_atom(UUID.uuid1())
        alternate_feed_map = %{uuid => %{title: attr[:title], url: attr[:href]}}
        %{feed: %{alternate_feeds: alternate_feed_map}}
      "payment" -> %{payment_link_title: attr[:title],
                     payment_link_url: String.slice(attr[:href], 0, 255)}
    end
  end

  def call(_, "tag", [:"rawvoice:donate", attr, [value]]), do: %{payment_link_title: value,
                                                                 payment_link_url: attr[:href]}

  def call(_, "tag", [:"atom10:link", attr, _]) do
    case attr[:rel] do
      "self"  -> %{feed: %{ self_link_title: attr[:title],
                            self_link_url: attr[:href]}}
      "hub" -> %{}
    end
  end

# tags to ignore
  def call(map, "tag", [tag_atom, _, _]) when tag_atom in [
    :"feedpress:locale", :"fyyd:verify", :"itunes:block", :"itunes:keywords", :"media:thumbnail",
    :"media:keywords", :"media:category", :category, :site, :docs, :"feedburner:info",
    :"media:credit", :"media:copyright", :"media:rating", :"media:description", :"copyright",
    :"feedburner:feedFlare", :"geo:lat", :"geo:long", :"creativeCommons:license",
    :"feedburner:emailServiceId", :"feedburner:feedburnerHostname", :managingEditor, :pubDate,
    :"sy:updatePeriod", :"sy:updateFrequency", :"wfw:commentRss", :"rawvoice:subscribe",
    :webMaster, :ttl, :"itunes:new-feed-url", :"googleplay:description", :"googleplay:email",
    :"googleplay:category", :"rawvoice:rating", :"rawvoice:location", :"rawvoice:frequency",
    :"ppg:seriesDetails", :"ppg:systemRef", :"ppg:network", :cloud, :"googleplay:image",
    :"googleplay:author", :"googleplay:explicit", :feed, :webmaster, :ilink, :ffmpeg, :domain,
    :lame, :broadcastlimit, :"itunes:link", :"channelExportDir", :"atom:id",
    :"openSearch:totalResults", :"openSearch:startIndex", :"openSearch:itemsPerPage", :"html",
    :"managingeditor", :"ard:programInformation", :"dc:creator", :"itunes:complete", :feedType,
    :changefreq
  ], do: map

  def call(_, "episode", [tag_atom, _, _]) when tag_atom in [
    :"googleplay:description", :"googleplay:image", :"googleplay:explicit", :"googleplay:block",
    :"frn:id", :"frn:title", :"frn:language", :"frn:art", :"frn:radio", :"frn:serie", :"frn:laenge",
    :"frn:licence", :"frn:last_update", :"itunes:keywords", :"post-id", :author, :"itunes:explicit",
    :category, :"dc:creator", :comments, :"media:content", :"feedburner:origLink", :"itunes:image",
    :"feedburner:origEnclosureLink", :"wfw:commentRss", :"slash:comments", :"itunes:block",
    :"itunes:order", :"ppg:canonical", :"cba:productionDate", :"cba:broadcastDate", :payment,
    :"cba:containsCopyright", :"media:thumbnail", :image, :source, :"media:description", :programid,
    :poddid, :"dcterms:modified", :"dcterms:created", :toPubDate, :audioId, :"atom:updated",
    :"thr:total", :"ard:visibility", :"series:name", :"rawvoice:poster", :"georss:point",
    :"copyright", :"ard:programInformation", :"sc:chapters", :"xhtml:body"
  ], do: %{}

# We expect several language tags
  def call(_, "tag", [:language, _, [value]]) do
    uuid = String.to_atom(UUID.uuid1())
    %{languages: %{uuid => %{shortcode: value}}}
  end

# We expect several contributors
  def call(map, "tag", [:"atom:contributor", _, value]) do
    Iterator.parse(map, "contributor", value, UUID.uuid1())
  end

  def call("contributor", [:"atom:name", _, [value]]), do: %{name: value}
  def call("contributor", [:"atom:uri",  _, [value]]), do: %{uri: value}

# We expect one owner
  def call(map, "tag", [:"itunes:owner", _, value]), do: Iterator.parse(map, "owner", value)
  def call(_, "owner", [:"itunes:name",   _, []]), do: %{}
  def call(_, "owner", [:"itunes:name",   _, [value]]), do: %{name: value}
  def call(_, "owner", [:"itunes:email",  _, []]), do: %{}
  def call(_, "owner", [:"itunes:email",  _, [value]]), do: %{email: value}

# Parsing categories infintely deep
  def call(_, "tag", [:"itunes:category", attr, []]) do
    {:ok, category} = Pan.Parser.Category.find_or_create(attr[:text], nil)
    %{categories: %{category.id => true}}
  end
  def call(_, "tag", [:"itunes:category", [], [value]]) do
    {:ok, category} = Pan.Parser.Category.find_or_create(value, nil)
    %{categories: %{category.id => true}}
  end
  def call(_, "tag", [:"itunes:category", attr, value]) do
    {:ok, category} = Pan.Parser.Category.find_or_create(attr[:text], nil)
    Iterator.parse(%{categories: %{category.id => true}}, "category", value, category.id)
  end

  def call("category", [:"itunes:category", attr, []], parent_id) do
    {:ok, category} = Pan.Parser.Category.find_or_create(attr[:text], parent_id)
    %{categories: %{category.id => true}}
  end

  def call("category", [:"itunes:category", attr, value], parent_id) do
    {:ok, category} = Pan.Parser.Category.find_or_create(attr[:text], parent_id)
    Iterator.parse(%{categories: %{category.id => true}}, "category", value, category.id)
  end

# Episodes
  def call(map, "tag", [:item, _, value]), do: Iterator.parse(map, "episode", value, UUID.uuid1())

  def call(_, "episode", [:title,             _, [value]]), do: %{title:       String.slice(value, 0, 255)}
  def call(_, "episode", [:link,              _, [value]]), do: %{link:        String.slice(value, 0, 255)}
  def call(_, "episode", [:guid,              _, [value]]), do: %{guid:        String.slice(value, 0, 255)}
  def call(_, "episode", [:guid,              _, _]), do: %{}
  def call(_, "episode", [:description,       _, []]), do: %{}
  def call(_, "episode", [:description,       _, [value | _]]), do: %{description: HtmlSanitizeEx2.basic_html_reduced(value)}
  def call(_, "episode", [:"content:encoded", _, []]), do: %{}
  def call(_, "episode", [:"content:encoded", _, [value]]), do: %{shownotes: HtmlSanitizeEx2.basic_html_reduced(value)}
  def call(_, "episode", [:"itunes:summary",  _, []]), do: %{}
  def call(_, "episode", [:"itunes:summary",  _, [value | _]]), do: %{summary: HtmlSanitizeEx2.basic_html_reduced(value)}
  def call(_, "episode", [:"itunes:subtitle", _, []]), do: %{}
  def call(_, "episode", [:"itunes:subtitle", _, [value]]), do: %{subtitle: String.slice(value, 0, 255)}
  def call(_, "episode", [:"itunes:author",   _, []]), do: %{}
  def call(_, "episode", [:"itunes:author",   _, [value]]), do: %{author: String.slice(value, 0, 255)}
  def call(_, "episode", [:"itunes:duration", _, []]), do: %{}
  def call(_, "episode", [:"itunes:duration", _, [value]]), do: %{duration: value}

  def call(_, "episode", [:pubDate,           _, [value]]) do
    %{publishing_date: Helpers.to_ecto_datetime(value)}
  end

  def call(_, "episode", [:"atom:link", attr, _]) do
    case attr[:rel] do
      "http://podlove.org/deep-link" -> %{deep_link: String.slice(attr[:href], 0, 255)}
      "payment" ->                      %{payment_link_title: attr[:title],
                                          payment_link_url: String.slice(attr[:href], 0, 255)}
    end
  end

# Enclosures a.k.a. Audiofiles
  def call(_, "episode", [:enclosure, attr, _]) do
    enclosure_map = %{url: attr[:url], length: attr[:length], type: attr[:type], guid: attr[:"bitlove:guid"]}
    uuid = String.to_atom(UUID.uuid1())
    %{enclosures: %{uuid => enclosure_map}}
  end

# Chapters
  def call(_, "episode", [:"psc:chapters", _, value]) do
    Iterator.parse(%{}, "chapter", value)
  end

  def call("chapter", [:"psc:chapter", attr, _]) do
    chapter_uuid = String.to_atom(UUID.uuid1())
    %{chapter_uuid => %{start: attr[:start], title: String.slice(attr[:title], 0, 255)}}
  end

# Episode contributors
  def call(_, "episode", [:"atom:contributor", _, value]) do
    contributor_uuid = String.to_atom(UUID.uuid1())
    Iterator.parse(%{contributors: %{contributor_uuid => %{}}}, "episode-contributor", value, contributor_uuid)
  end

  def call("episode-contributor", [:"atom:name", _ , [value]]), do: %{name: value}
  def call("episode-contributor", [:"atom:uri",  _ , [value]]), do: %{uri: value}

# Show debugging information for unknown tags on console
  def call(_, mode, [tag, attr, value]) do
    IO.puts "\n\e[96m === Tag unknown: ==="
    IO.puts "Mode: " <> mode
    IO.puts "Tag: " <> ~s/:"/ <> to_string(tag) <> ~s/"/
    IO.puts "Attr: "
    IO.inspect attr
    IO.puts "Value: "
    IO.inspect value
    IO.puts " =================\e[0m"
    raise "Tag unknown"
  end
end


Add code doc for Kitto.Job.DSL

Add code doc for Kitto.Job.DSL

defmodule Kitto.Job.DSL do
  @moduledoc """
  A DSL to define jobs populating the widgets with data.
  """

  @doc false
  defmacro __using__(_opts) do
    quote do
      import Kitto.Job.DSL
      import Kitto.Notifier, only: [broadcast!: 2]
    end
  end

  @doc """
  Main API to define jobs.

  It accepts an expression representing data retrieval and any transformations
  required to broadcast events to the widgets.

  ## Examples

      use Kitto.Job.DSL

      job :jenkins, every: :minute do
        jobs = Jenkins.jobs |> Enum.map(fn (%{"job" => job}) -> %{job: job.status} end)

        broadcast! :jenkins, %{jobs: jobs}
      end

      job :twitter, do: Twitter.stream("#elixir", &(broadcast!(:twitter, &1))

  ## Options
    * `:every` - Sets the interval on which the job will be performed. When it's not
    specified, the job will be called once (suitable for streaming resources).

    * `:first_at` - A timeout after which to perform the job for the first time
  """
  defmacro job(name, options, contents \\ []) do
    quote do
      Kitto.Job.register unquote(name),
                         unquote(options),
                         (__ENV__ |> Map.take([:file, :line])),
                         fn -> unquote(contents[:do]) end
    end
  end
end

defmodule Kitto.Job.DSL do
  defmacro __using__(_opts) do
    quote do
      import Kitto.Job.DSL
      import Kitto.Notifier, only: [broadcast!: 2]
    end
  end

  defmacro job(name, options, contents \\ []) do
    quote do
      Kitto.Job.register unquote(name),
                         unquote(options),
                         (__ENV__ |> Map.take([:file, :line])),
                         fn -> unquote(contents[:do]) end
    end
  end
end


Remove raise when no key is provide at compile time

defmodule SendGrid do
  @moduledoc """
  Base module for interacting with SendGrid's API. A configured API key must
  be provided in your applications project's configuration.

  ## Configuration

  You must provide a configuration which includes your `api_key`.

  ```
  config :sendgrid,
    api_key: "sendgrid_api_key"
  ```
  """

  use HTTPoison.Base

  @api_url "https://api.sendgrid.com"

  defp process_url(url) do
    @api_url <> url
  end

  defp api_key do
    Application.get_env(:sendgrid, :api_key)
  end

  # Default headers to be sent.
  defp base_headers do
     %{
       "Content-Type" => "application/json",
       "Authorization" => "Bearer #{api_key}"
     }
  end

  defp process_request_body(body) when is_binary(body), do: body
  defp process_request_body(body), do: Poison.encode!(body)

  # Override the base headers with any passed in.
  defp process_request_headers(request_headers) do
    headers =
      request_headers
      |> Enum.into(%{})

    Map.merge(base_headers, headers)
    |> Enum.into([])
  end

  defp process_response_body(body) do
    case Poison.decode(body) do
      { :ok, data } -> data
      _ -> body
    end
  end

end

defmodule SendGrid do
  @moduledoc """
  Base module for interacting with SendGrid's API. A configured API key must
  be provided in your applications project's configuration.

  ## Configuration

  You must provide a configuration which includes your `api_key`.

  ```
  config :sendgrid,
    api_key: "sendgrid_api_key"
  ```
  """

  use HTTPoison.Base

  @api_url "https://api.sendgrid.com"

  if !Application.get_env(:sendgrid, :api_key), do: raise "SendGrid is not configured."

  defp process_url(url) do
    @api_url <> url
  end

  # Default headers to be sent.
  defp base_headers do
     api_key = Application.get_env(:sendgrid, :api_key)
     %{
       "Content-Type" => "application/json",
       "Authorization" => "Bearer #{api_key}"
     }
  end

  defp process_request_body(body) when is_binary(body), do: body
  defp process_request_body(body), do: Poison.encode!(body)

  # Override the base headers with any passed in.
  defp process_request_headers(request_headers) do
    headers =
      request_headers
      |> Enum.into(%{})

    Map.merge(base_headers, headers)
    |> Enum.into([])
  end

  defp process_response_body(body) do
    case Poison.decode(body) do
      { :ok, data } -> data
      _ -> body
    end
  end

end


Simplify simplify

Simplify simplify

Code.require_file "../test_helper.exs", __ENV__.file

defmodule RadpathTests.RadpathFacts do

  use Amrita.Sweet

  import PathHelpers

  alias :file, as: F

  facts "Test zipping" do

    defchecker path_exists(path) do
        File.exists?(path) |> truthy
    end

    fact "Test zip: List of directories" do
      try do
        Path.join(fixture_path, "Testdir1.zip") |> ! path_exists()
        Radpath.zip([fixture_path], "Testdir1.zip")
        "Testdir1.zip" |> path_exists()
        System.cmd("zip",["-T","Testdir1.zip"]) |> {"test of Testdir1.zip OK\n", 0}
        {result_str, code} = System.cmd("zipinfo",["-1","Testdir1.zip"])
        result_str |> contains "testdir1"
      after
        File.rm_rf("Testdir1.zip")
      end
    end

    fact "Test zip: One path" do

      dir = Path.join(fixture_path, "testdir2")

      try do
        Path.join(fixture_path, "Testdir2.zip") |> ! path_exists()
        Radpath.zip([dir], "Testdir2.zip")
        "Testdir2.zip" |> path_exists()
        {result_str, code} = System.cmd("zip",["-T","Testdir2.zip"])
        result_str |> String.strip |> "test of Testdir2.zip OK"
        {result_str2, code2} = System.cmd("zipinfo",["-1","Testdir2.zip"])
        result_str2 |> contains "testdir2"
      after
        File.rm_rf("Testdir2.zip")
      end
    end

  fact "Test zip: One bitstring path" do
    dir = Path.join(fixture_path, "testdir1")
    try do
      Path.join(fixture_path, "Testdir3.zip") |> ! path_exists()
      Radpath.zip(dir, "Testdir3.zip")
      "Testdir3.zip" |> path_exists()
      System.cmd("zip",["-T","Testdir3.zip"]) |>
				{"test of Testdir3.zip OK\n", 0}
      {result_str, code} = System.cmd("zipinfo",["-1","Testdir3.zip"])
      result_str |> contains "testdir1"
    after
      File.rm_rf("Testdir3.zip")
    end
  end

  fact "Test unzip: One bitstring path in cwd" do
    try do
      Path.join(fixture_path, "dome.csv") |> ! path_exists()
      Radpath.unzip(Path.join(fixture_path, "dome.zip"), fixture_path)
      Path.join(fixture_path, "dome.csv") |> path_exists()
    after
      File.rm_rf(Path.join(fixture_path, "dome.csv"))
    end
  end

  fact "Test unzip: One bitstring path in tmp" do
    try do
      Path.join(fixture_path, "dome.csv") |> ! path_exists()
      Radpath.unzip(Path.join(fixture_path, "dome.zip"), "/tmp")
    after
			Path.join("/tmp", "dome.csv") |> path_exists()
      File.rm_rf("/tmp/dome.csv")
    end
  end

  fact "Test unzip: Non existent zip file will result in nil" do
    Radpath.unzip("gogo/gaga/none.zip") |> nil
  end

  fact "Test zip: Path that does not exist" do
      try do
        Path.join(fixture_path, "Testdir-dont-exist.zip") |>
					! path_exists()
        Radpath.zip(["/gogo/I/don/exist"], "Testdir-dont-exist.zip") |> nil
			after
        "Testdir-dont-exist.zip" |> ! path_exists()
      rescue
        e in RuntimeError -> e
      end
    end
  end

  facts "Test Filtering" do

		@dud_files ["testfile1.dud, file3.dud"]
		@test_files ["testdir3", "testdir2", "testdir1"]
		@file_list ["file1.txt", "file2.txt", "file3.log"]
		@file_ext ["txt", "log"]
    @long_file_list @file_list ++ ["testfile1.txt", "testfile2.txt",
																	 "testfile3.txt"]

    fact "Test Filtering: Files" do
      files = Radpath.files(fixture_path, "txt") |> Enum.map(&Path.basename(&1))
			Enum.map(@dud_files, fn(x) -> files |> ! contains x end)
      Enum.all?(@file_list -- ["file3.log"], fn(x) -> files |> contains x end)
    end

    fact "Test Filtering: Files returns empty list if path does not exist" do
      Radpath.files("/this/path/does/not/exist") |> []
    end

    fact "Test Filtering: Directories" do
      dirs = Radpath.dirs(fixture_path) |> Enum.map(&Path.basename(&1))
			Enum.map(@dud_files, fn(x) -> dirs |> ! contains x end)
      Enum.all?(@test_files, fn(x) -> dirs |> contains x end)
    end

    fact "Test Filtering: List of Directories" do
			expected = @test_files ++ ["fixtures", "gogo"] |> Enum.sort
      Radpath.dirs(["test", "lib"]) |>
				Enum.map(&Path.basename(&1)) |>
				equals expected ++ ["Radpath"]
    end

    fact "Test Filtering: Regex Directories" do
			Radpath.dirs("test", "fixtures") |>
			Enum.map(&Path.basename(&1)) |> ["fixtures"]
    end

    fact "Test Filtering: files" do
			Radpath.files(fixture_path, "log") |>
      Enum.map(&Path.basename(&1)) |>
			equals ["file3.log"]
    end

    fact "Test Filtering: files with lists" do
      Radpath.files(["test", "lib"], @file_ext)
      |> Enum.map(&Path.basename(&1))
      |> Enum.sort
			|> equals @long_file_list
    end

    fact "Test Filtering: files. Expanded path works too." do
      Radpath.files("test/fixtures", "log") |>
			Enum.map(&Path.basename(&1)) |>
			equals ["file3.log"]
    end

    fact "Test Filtering: Multiple filter for files function" do
      files = Radpath.files(fixture_path, @file_ext) |>
				Enum.map(&Path.basename(&1))
			files |> Enum.sort |> equals @long_file_list
      @file_list |> for_all (&Enum.member?(files, &1))
    end

    fact "Test Filtering: Multiple filter for files function if extension is list of char list" do
      files = Radpath.files(fixture_path, @file_ext) |>
				Enum.map(&Path.basename(&1))
			files |> Enum.sort |> equals @long_file_list
      @file_list |> for_all (&Enum.member?(files, &1))
    end

    fact "Test Filtering: Multiple filter for files function if extension and paths is list of char list" do
      files = Radpath.files(['lib'], @file_ext) |>
				Enum.map(&Path.basename(&1))
      @file_list |> for_all (&Enum.member?(files, &1))
    end
  end

  facts "Test symlink" do

		@src Path.join(fixture_path, "testdir3")
    @dest Path.join(Path.expand("."), "testdir3")

    fact "Test symlink: Normal Usage" do
      try do
        @dest |> ! path_exists()
        Radpath.symlink(@src, @dest)
      after
				elem(F.read_link(@dest), 0) |> :ok
        Radpath.islink?(@dest) |> truthy
				@dest |> path_exists()
        File.rm_rf @dest
      end
    end

    fact "Test symlink: For non existent src file" do

      src = Path.join(fixture_path, "testdir3xx")

      try do
        @dest |> ! path_exists
        Radpath.symlink(src, @dest)
        F.read_link(@dest) |> {:error, :enoent}
        @dest |> ! path_exists
      after
        File.rm_rf @dest
      end
    end

    fact "Test symlink: islink? Return true if path is symlink" do

      test_dest = Path.join(fixture_path, "test_symlink")

      try do
        test_dest |> ! path_exists
        Radpath.symlink(fixture_path, test_dest)
        Radpath.islink?(test_dest) |> truthy
      after
        File.rm_rf test_dest
      end
    end

    fact "Test symlink: islink? Return false if path is not symlink" do
      Radpath.islink?(fixture_path) |> falsey
    end

    fact "Test symlink: islink? Return false if path does not exist" do
      Radpath.islink?("/I/wiLL/neveR/exist") |> falsey
    end

  end

  facts "Test Tempfilefs" do

    fact "Test Tempfilefs: Without Argument" do
        tmpdirpath1 = Radpath.mktempdir
        try do
          Radpath.mktempdir |> File.exists?
        after
          File.rm_rf tmpdirpath1
        end
    end

    fact "Test mktempdir: With argument" do
      src = Path.join(fixture_path, "testdir3")
      tmpdirpath = Radpath.mktempdir(src)

      try do
        File.exists?(tmpdirpath) |> truthy
      after
        File.rm_rf tmpdirpath
      end
    end

    fact "Test mktempdir: Nonexistant parent path, error returned" do
      tmpdirpath = Radpath.mktempdir("/gogo/gaga/gigi")
      try do
        tmpdirpath |> {:error, :enoent}
      rescue
        e in RuntimeError -> e
      end
    end
  end

  facts "Test rename and mv" do

		@source_file "/tmp/hoho.txt"
		@dest_file "/tmp/hehe.txt"

    fact "Test rename: Normal Usage" do
      File.touch!(@source_file)
      try do
        @source_file |> path_exists()
        Radpath.rename(@source_file, @dest_file)
        @source_file |> ! path_exists()
        @dest_file |> path_exists()
      after
        File.rm_rf @dest_file
      end
    end

    fact "Test mv: Normal Usage" do
      File.touch!(@source_file)
      try do
        @source_file |> path_exists()
        Radpath.mv(@source_file, @dest_file)
        @source_file |> ! path_exists()
        @dest_file |> path_exists()
      after
        File.rm_rf @dest_file
      end
    end

    fact "Test rename: Source file does not exist" do
      try do
      	@source_file |> ! path_exists()
      	Radpath.rename(@source_file, @dest_file)
      	@dest_file |> ! path_exists()
      after
      	File.rm_rf @source_file
      	File.rm_rf @dest_file
      end
    end

    fact "Test mv: Source file does not exist" do
      try do
      	@source_file |> ! path_exists()
      	Radpath.mv(@source_file, @dest_file)
      	@dest_file |> ! path_exists()
      after
      	File.rm_rf @source_file
      	File.rm_rf @dest_file
      end
    end

    fact "Test mv: Destination folder does not exist" do
      source_file = "mix.exs"
      dest_file = "/tmp/xxxyyyzzz/mix.exs"
      Radpath.mv(source_file, dest_file) |> {:error, :enoent}
    end

  end

  facts "Test relative paths" do
    fact "Test relative_path: Normal Usage" do
      Radpath.relative_path("/tmp/base", "/tmp/base/hoho.txt") |> "hoho.txt"
    end

    fact "Test relative_path: If same path is given empty string will result" do
      Radpath.relative_path("/tmp/base/", "/tmp/base/") |> ""
    end
  end

  facts "Test ensure" do

    fact "Test ensure: Ensure Directory is created" do
      test_dir_path = Path.join(fixture_path, "gogo/gaga")
      test_dir_path |> ! path_exists()
      try do
        Radpath.ensure(test_dir_path)
        test_dir_path |> path_exists()
      after
        File.rm_rf(test_dir_path)
      end
    end

    fact "Test ensure: Ensure File is created" do
      test_file_path = Path.join(fixture_path, "gogo/gaga.txt")
      test_file_path |> ! path_exists()
      try do
        Radpath.ensure(test_file_path)
        test_file_path |> path_exists()
      after
        File.rm_rf(test_file_path)
      end
    end
  end

  facts "Test Other functions:" do
    fact "Test md5sum: md5sum function" do
      [h | _]= String.split(to_string(:os.cmd('md5sum mix.exs')))
      assert h == Radpath.md5sum("mix.exs")
    end
    fact "Test sha1sum: sha1sum function" do
      [h | _]= String.split(to_string(:os.cmd('sha1sum mix.exs')))
      assert h == Radpath.sha1sum("mix.exs")
    end
    fact "Test sha1sum: sha1sum on directory" do
      Radpath.sha1sum("/tmp") |> :error
    end
    fact "Test md5sum: md5sum on directory" do
      Radpath.md5sum("/tmp") |> :error
    end
    fact "Test md5sum: md5sum on non existent directory" do
      Radpath.md5sum("xxx") |> :error
    end
    fact "Test ParentPath: Return parent path of string" do
      Radpath.parent_path("/I/am/long/dir") |> "/I/am/long/"
    end
  end
end

Code.require_file "../test_helper.exs", __ENV__.file

defmodule RadpathTests.RadpathFacts do

  use Amrita.Sweet

  import PathHelpers

  alias :file, as: F

  facts "Test zipping" do

    defchecker path_exists(path) do
        File.exists?(path) |> truthy
    end

    fact "Test zip: List of directories" do
      try do
        Path.join(fixture_path, "Testdir1.zip") |> ! path_exists()
        Radpath.zip([fixture_path], "Testdir1.zip")
        "Testdir1.zip" |> path_exists()
        System.cmd("zip",["-T","Testdir1.zip"]) |> {"test of Testdir1.zip OK\n", 0}
        {result_str, code} = System.cmd("zipinfo",["-1","Testdir1.zip"])
        result_str |> contains "testdir1"
      after
        File.rm_rf("Testdir1.zip")
      end
    end

    fact "Test zip: One path" do

      dir = Path.join(fixture_path, "testdir2")

      try do
        Path.join(fixture_path, "Testdir2.zip") |> ! path_exists()
        Radpath.zip([dir], "Testdir2.zip")
        "Testdir2.zip" |> path_exists()
        {result_str, code} = System.cmd("zip",["-T","Testdir2.zip"])
        result_str |> String.strip |> "test of Testdir2.zip OK"
        {result_str2, code2} = System.cmd("zipinfo",["-1","Testdir2.zip"])
        result_str2 |> contains "testdir2"
      after
        File.rm_rf("Testdir2.zip")
      end
    end

  fact "Test zip: One bitstring path" do
    dir = Path.join(fixture_path, "testdir1")
    try do
      Path.join(fixture_path, "Testdir3.zip") |> ! path_exists()
      Radpath.zip(dir, "Testdir3.zip")
      "Testdir3.zip" |> path_exists()
      System.cmd("zip",["-T","Testdir3.zip"]) |>
				{"test of Testdir3.zip OK\n", 0}
      {result_str, code} = System.cmd("zipinfo",["-1","Testdir3.zip"])
      result_str |> contains "testdir1"
    after
      File.rm_rf("Testdir3.zip")
    end
  end

  fact "Test unzip: One bitstring path in cwd" do
    try do
      Path.join(fixture_path, "dome.csv") |> ! path_exists()
      Radpath.unzip(Path.join(fixture_path, "dome.zip"), fixture_path)
      Path.join(fixture_path, "dome.csv") |> path_exists()
    after
      File.rm_rf(Path.join(fixture_path, "dome.csv"))
    end
  end

  fact "Test unzip: One bitstring path in tmp" do
    try do
      Path.join(fixture_path, "dome.csv") |> ! path_exists()
      Radpath.unzip(Path.join(fixture_path, "dome.zip"), "/tmp")
    after
			Path.join("/tmp", "dome.csv") |> path_exists()
      File.rm_rf("/tmp/dome.csv")
    end
  end

  fact "Test unzip: Non existent zip file will result in nil" do
    Radpath.unzip("gogo/gaga/none.zip") |> nil
  end

  fact "Test zip: Path that does not exist" do
      try do
        Path.join(fixture_path, "Testdir-dont-exist.zip") |>
					! path_exists()
        Radpath.zip(["/gogo/I/don/exist"], "Testdir-dont-exist.zip") |> nil
			after
        "Testdir-dont-exist.zip" |> ! path_exists()
      rescue
        e in RuntimeError -> e
      end
    end
  end

  facts "Test Filtering" do

		@dud_files ["testfile1.dud, file3.dud"]
		@test_files ["testdir3", "testdir2", "testdir1"]
		@file_list ["file1.txt", "file2.txt", "file3.log"]
		@file_ext ["txt", "log"]
    @long_file_list @file_list ++ ["testfile1.txt", "testfile2.txt",
																	 "testfile3.txt"]

    fact "Test Filtering: Files" do
      files = Radpath.files(fixture_path, "txt") |> Enum.map(&Path.basename(&1))
			Enum.map(@dud_files, fn(x) -> files |> ! contains x end)
      Enum.all?(@file_list -- ["file3.log"], fn(x) -> files |> contains x end)
    end

    fact "Test Filtering: Files returns empty list if path does not exist" do
      Radpath.files("/this/path/does/not/exist") |> []
    end

    fact "Test Filtering: Directories" do
      dirs = Radpath.dirs(fixture_path) |> Enum.map(&Path.basename(&1))
			Enum.map(@dud_files, fn(x) -> dirs |> ! contains x end)
      Enum.all?(@test_files, fn(x) -> dirs |> contains x end)
    end

    fact "Test Filtering: List of Directories" do
      dirs = Radpath.dirs(["test", "lib"]) |> Enum.map(&Path.basename(&1))
			Enum.all?(@test_files ++ ["Radpath"], fn(x) -> dirs |> contains x end)
    end

    fact "Test Filtering: Regex Directories" do
			Radpath.dirs("test", "fixtures") |>
			Enum.map(&Path.basename(&1)) |> ["fixtures"]
    end

    fact "Test Filtering: files" do
			Radpath.files(fixture_path, "log") |>
      Enum.map(&Path.basename(&1)) |>
			equals ["file3.log"]
    end

    fact "Test Filtering: files with lists" do
      Radpath.files(["test", "lib"], @file_ext)
      |> Enum.map(&Path.basename(&1))
      |> Enum.sort
			|> equals @long_file_list
    end

    fact "Test Filtering: files. Expanded path works too." do
      Radpath.files("test/fixtures", "log") |>
			Enum.map(&Path.basename(&1)) |>
			equals ["file3.log"]
    end

    fact "Test Filtering: Multiple filter for files function" do
      files = Radpath.files(fixture_path, @file_ext) |>
				Enum.map(&Path.basename(&1))
			files |> Enum.sort |> equals @long_file_list
      @file_list |> for_all (&Enum.member?(files, &1))
    end

    fact "Test Filtering: Multiple filter for files function if extension is list of char list" do
      files = Radpath.files(fixture_path, @file_ext) |>
				Enum.map(&Path.basename(&1))
			files |> Enum.sort |> equals @long_file_list
      @file_list |> for_all (&Enum.member?(files, &1))
    end

    fact "Test Filtering: Multiple filter for files function if extension and paths is list of char list" do
      files = Radpath.files(['lib'], @file_ext) |>
				Enum.map(&Path.basename(&1))
      @file_list |> for_all (&Enum.member?(files, &1))
    end
  end

  facts "Test symlink" do

		@src Path.join(fixture_path, "testdir3")
    @dest Path.join(Path.expand("."), "testdir3")

    fact "Test symlink: Normal Usage" do
      try do
        @dest |> ! path_exists()
        Radpath.symlink(@src, @dest)
      after
				elem(F.read_link(@dest), 0) |> :ok
        Radpath.islink?(@dest) |> truthy
				@dest |> path_exists()
        File.rm_rf @dest
      end
    end

    fact "Test symlink: For non existent src file" do

      src = Path.join(fixture_path, "testdir3xx")

      try do
        @dest |> ! path_exists
        Radpath.symlink(src, @dest)
        F.read_link(@dest) |> {:error, :enoent}
        @dest |> ! path_exists
      after
        File.rm_rf @dest
      end
    end

    fact "Test symlink: islink? Return true if path is symlink" do

      test_dest = Path.join(fixture_path, "test_symlink")

      try do
        test_dest |> ! path_exists
        Radpath.symlink(fixture_path, test_dest)
        Radpath.islink?(test_dest) |> truthy
      after
        File.rm_rf test_dest
      end
    end

    fact "Test symlink: islink? Return false if path is not symlink" do
      Radpath.islink?(fixture_path) |> falsey
    end

    fact "Test symlink: islink? Return false if path does not exist" do
      Radpath.islink?("/I/wiLL/neveR/exist") |> falsey
    end

  end

  facts "Test Tempfilefs" do

    fact "Test Tempfilefs: Without Argument" do
        tmpdirpath1 = Radpath.mktempdir
        try do
          Radpath.mktempdir |> File.exists?
        after
          File.rm_rf tmpdirpath1
        end
    end

    fact "Test mktempdir: With argument" do
      src = Path.join(fixture_path, "testdir3")
      tmpdirpath = Radpath.mktempdir(src)

      try do
        File.exists?(tmpdirpath) |> truthy
      after
        File.rm_rf tmpdirpath
      end
    end

    fact "Test mktempdir: Nonexistant parent path, error returned" do
      tmpdirpath = Radpath.mktempdir("/gogo/gaga/gigi")
      try do
        tmpdirpath |> {:error, :enoent}
      rescue
        e in RuntimeError -> e
      end
    end
  end

  facts "Test rename and mv" do

		@source_file "/tmp/hoho.txt"
		@dest_file "/tmp/hehe.txt"

    fact "Test rename: Normal Usage" do
      File.touch!(@source_file)
      try do
        @source_file |> path_exists()
        Radpath.rename(@source_file, @dest_file)
        @source_file |> ! path_exists()
        @dest_file |> path_exists()
      after
        File.rm_rf @dest_file
      end
    end

    fact "Test mv: Normal Usage" do
      File.touch!(@source_file)
      try do
        @source_file |> path_exists()
        Radpath.mv(@source_file, @dest_file)
        @source_file |> ! path_exists()
        @dest_file |> path_exists()
      after
        File.rm_rf @dest_file
      end
    end

    fact "Test rename: Source file does not exist" do
      try do
      	@source_file |> ! path_exists()
      	Radpath.rename(@source_file, @dest_file)
      	@dest_file |> ! path_exists()
      after
      	File.rm_rf @source_file
      	File.rm_rf @dest_file
      end
    end

    fact "Test mv: Source file does not exist" do
      try do
      	@source_file |> ! path_exists()
      	Radpath.mv(@source_file, @dest_file)
      	@dest_file |> ! path_exists()
      after
      	File.rm_rf @source_file
      	File.rm_rf @dest_file
      end
    end

    fact "Test mv: Destination folder does not exist" do
      source_file = "mix.exs"
      dest_file = "/tmp/xxxyyyzzz/mix.exs"
      Radpath.mv(source_file, dest_file) |> {:error, :enoent}
    end

  end

  facts "Test relative paths" do
    fact "Test relative_path: Normal Usage" do
      Radpath.relative_path("/tmp/base", "/tmp/base/hoho.txt") |> "hoho.txt"
    end

    fact "Test relative_path: If same path is given empty string will result" do
      Radpath.relative_path("/tmp/base/", "/tmp/base/") |> ""
    end
  end

  facts "Test ensure" do

    fact "Test ensure: Ensure Directory is created" do
      test_dir_path = Path.join(fixture_path, "gogo/gaga")
      test_dir_path |> ! path_exists()
      try do
        Radpath.ensure(test_dir_path)
        test_dir_path |> path_exists()
      after
        File.rm_rf(test_dir_path)
      end
    end

    fact "Test ensure: Ensure File is created" do
      test_file_path = Path.join(fixture_path, "gogo/gaga.txt")
      test_file_path |> ! path_exists()
      try do
        Radpath.ensure(test_file_path)
        test_file_path |> path_exists()
      after
        File.rm_rf(test_file_path)
      end
    end
  end

  facts "Test Other functions:" do
    fact "Test md5sum: md5sum function" do
      [h | _]= String.split(to_string(:os.cmd('md5sum mix.exs')))
      assert h == Radpath.md5sum("mix.exs")
    end
    fact "Test sha1sum: sha1sum function" do
      [h | _]= String.split(to_string(:os.cmd('sha1sum mix.exs')))
      assert h == Radpath.sha1sum("mix.exs")
    end
    fact "Test sha1sum: sha1sum on directory" do
      Radpath.sha1sum("/tmp") |> :error
    end
    fact "Test md5sum: md5sum on directory" do
      Radpath.md5sum("/tmp") |> :error
    end
    fact "Test md5sum: md5sum on non existent directory" do
      Radpath.md5sum("xxx") |> :error
    end
    fact "Test ParentPath: Return parent path of string" do
      Radpath.parent_path("/I/am/long/dir") |> "/I/am/long/"
    end
  end
end


General endpoint

General endpoint

defmodule Bot.FacebookController do
  use Bot.Web, :controller
  @verify_token System.get_env("FACEBOOK_VERIFICATION_TOKEN")

  def webhook(conn, %{
    "hub.verify_token" => verify_token,
    "hub.challenge" => challenge})
  do
    IO.puts(@verify_token)
    IO.puts(verify_token)
    case verify_token do
      @verify_token -> text conn, challenge
      _ ->             text conn, "WRONG"
    end
  end
  def webhook(conn, _), do: text conn, "I have no idea of how to respond this"
  def handle_message(conn, _params) do
      text conn, "Responding Message"
  end
  # def handle_message(conn, _params = %{"entry" => entry}) do
  #     %{"id" => _, "messaging" => messaging} = hd(entry)

  #     messaging
  #     |> Enum.each(&Bot.MessageHandler.handle/1)
  #     text conn, "Responding Message"
  # end

end

defmodule Bot.FacebookController do
  use Bot.Web, :controller
  @verify_token System.get_env("FACEBOOK_VERIFICATION_TOKEN")

  def webhook(conn, %{
    "hub.verify_token" => verify_token,
    "hub.challenge" => challenge})
  do
    case verify_token do
      @verify_token -> text conn, challenge
      _ ->             text conn, "WRONG"
    end
  end
  def webhook(conn, _), do: text conn, "I have no idea of how to respond this"

  def handle_message(conn, _params = %{"entry" => entry}) do
      %{"id" => _, "messaging" => messaging} = hd(entry)

      messaging
      |> Enum.each(&Bot.MessageHandler.handle/1)
      text conn, "Responding Message"
  end

end