Remove unused `opts` arguments

Remove unused `opts` arguments

defmodule Strava.Athletes do
  @moduledoc """
  API calls for all endpoints tagged `Athletes`.
  """

  alias Strava.Client
  import Strava.RequestBuilder

  @doc """
  Get Authenticated Athlete
  Returns the currently authenticated athlete. Tokens with profile:read_all
  scope will receive a detailed athlete representation; all others will receive
  a summary representation.

  ## Parameters

  - client (Strava.Client): Client to make authenticated requests

  ## Returns

  {:ok, %Strava.DetailedAthlete{}} on success
  {:error, info} on failure
  """
  @spec get_logged_in_athlete(Tesla.Env.client()) ::
          {:ok, Strava.DetailedAthlete.t()} | {:error, Tesla.Env.t()}
  def get_logged_in_athlete(client) do
    request =
      %{}
      |> method(:get)
      |> url("/athlete")
      |> Enum.into([])

    client |> Client.request(request) |> decode(%Strava.DetailedAthlete{})
  end

  @doc """
  Get Zones

  Returns the the authenticated athlete's heart rate and power zones.
  Requires `profile:read_all`.

  ## Parameters

  - client (Strava.Client): Client to make authenticated requests

  ## Returns

  {:ok, %Strava.Zones{}} on success
  {:error, info} on failure
  """
  @spec get_logged_in_athlete_zones(Tesla.Env.client()) ::
          {:ok, Strava.Zones.t()} | {:error, Tesla.Env.t()}
  def get_logged_in_athlete_zones(client) do
    request =
      %{}
      |> method(:get)
      |> url("/athlete/zones")
      |> Enum.into([])

    client |> Client.request(request) |> decode(%Strava.Zones{})
  end

  @doc """
  Get Athlete Stats

  Returns the activity stats of an athlete.

  ## Parameters

  - client (Strava.Client): Client to make authenticated requests
  - id (integer()): The identifier of the athlete. Must match the authenticated athlete.
  - opts (KeywordList): [optional] Optional parameters
    - :page (integer()): Page number.
    - :per_page (integer()): Number of items per page. Defaults to 30.

  ## Returns

  {:ok, %Strava.ActivityStats{}} on success
  {:error, info} on failure
  """
  @spec get_stats(Tesla.Env.client(), integer(), keyword()) ::
          {:ok, Strava.ActivityStats.t()} | {:error, Tesla.Env.t()}
  def get_stats(client, id, opts \\ []) do
    optional_params = %{
      :page => :query,
      :per_page => :query
    }

    request =
      %{}
      |> method(:get)
      |> url("/athletes/#{id}/stats")
      |> add_optional_params(optional_params, opts)
      |> Enum.into([])

    client |> Client.request(request) |> decode(%Strava.ActivityStats{})
  end

  @doc """
  Update Athlete
  Update the currently authenticated athlete. Requires profile:write scope.

  ## Parameters

  - client (Strava.Client): Client to make authenticated requests
  - weight (float()): The weight of the athlete in kilograms.

  ## Returns

  {:ok, %Strava.DetailedAthlete{}} on success
  {:error, info} on failure
  """
  @spec update_logged_in_athlete(Tesla.Env.client(), float()) ::
          {:ok, Strava.DetailedAthlete.t()} | {:error, Tesla.Env.t()}
  def update_logged_in_athlete(client, weight) do
    request =
      %{}
      |> method(:put)
      |> url("/athlete")
      |> add_param(:query, :weight, weight)
      |> Enum.into([])

    client |> Client.request(request) |> decode(%Strava.DetailedAthlete{})
  end
end

defmodule Strava.Athletes do
  @moduledoc """
  API calls for all endpoints tagged `Athletes`.
  """

  alias Strava.Client
  import Strava.RequestBuilder

  @doc """
  Get Authenticated Athlete
  Returns the currently authenticated athlete. Tokens with profile:read_all scope will receive a detailed athlete representation; all others will receive a summary representation.

  ## Parameters

  - client (Strava.Client): Client to make authenticated requests
  - opts (KeywordList): [optional] Optional parameters
  ## Returns

  {:ok, %Strava.DetailedAthlete{}} on success
  {:error, info} on failure
  """
  @spec get_logged_in_athlete(Tesla.Env.client(), keyword()) ::
          {:ok, Strava.DetailedAthlete.t()} | {:error, Tesla.Env.t()}
  def get_logged_in_athlete(client, _opts \\ []) do
    request =
      %{}
      |> method(:get)
      |> url("/athlete")
      |> Enum.into([])

    client |> Client.request(request) |> decode(%Strava.DetailedAthlete{})
  end

  @doc """
  Get Zones
  Returns the the authenticated athlete's heart rate and power zones. Requires profile:read_all.

  ## Parameters

  - client (Strava.Client): Client to make authenticated requests
  - opts (KeywordList): [optional] Optional parameters
  ## Returns

  {:ok, %Strava.Zones{}} on success
  {:error, info} on failure
  """
  @spec get_logged_in_athlete_zones(Tesla.Env.client(), keyword()) ::
          {:ok, Strava.Zones.t()} | {:error, Tesla.Env.t()}
  def get_logged_in_athlete_zones(client, _opts \\ []) do
    request =
      %{}
      |> method(:get)
      |> url("/athlete/zones")
      |> Enum.into([])

    client |> Client.request(request) |> decode(%Strava.Zones{})
  end

  @doc """
  Get Athlete Stats
  Returns the activity stats of an athlete.

  ## Parameters

  - client (Strava.Client): Client to make authenticated requests
  - id (integer()): The identifier of the athlete. Must match the authenticated athlete.
  - opts (KeywordList): [optional] Optional parameters
    - :page (integer()): Page number.
    - :per_page (integer()): Number of items per page. Defaults to 30.
  ## Returns

  {:ok, %Strava.ActivityStats{}} on success
  {:error, info} on failure
  """
  @spec get_stats(Tesla.Env.client(), integer(), keyword()) ::
          {:ok, Strava.ActivityStats.t()} | {:error, Tesla.Env.t()}
  def get_stats(client, id, opts \\ []) do
    optional_params = %{
      :page => :query,
      :per_page => :query
    }

    request =
      %{}
      |> method(:get)
      |> url("/athletes/#{id}/stats")
      |> add_optional_params(optional_params, opts)
      |> Enum.into([])

    client |> Client.request(request) |> decode(%Strava.ActivityStats{})
  end

  @doc """
  Update Athlete
  Update the currently authenticated athlete. Requires profile:write scope.

  ## Parameters

  - client (Strava.Client): Client to make authenticated requests
  - weight (float()): The weight of the athlete in kilograms.
  - opts (KeywordList): [optional] Optional parameters
  ## Returns

  {:ok, %Strava.DetailedAthlete{}} on success
  {:error, info} on failure
  """
  @spec update_logged_in_athlete(Tesla.Env.client(), float(), keyword()) ::
          {:ok, Strava.DetailedAthlete.t()} | {:error, Tesla.Env.t()}
  def update_logged_in_athlete(client, weight, _opts \\ []) do
    request =
      %{}
      |> method(:put)
      |> url("/athlete")
      |> add_param(:query, :weight, weight)
      |> Enum.into([])

    client |> Client.request(request) |> decode(%Strava.DetailedAthlete{})
  end
end


fix: set title for note views

fix: set title for note views

defmodule Wwwtech.NoteView do
  use Wwwtech.Web, :view
  def page_title(:index, _), do: "Notes"

  def page_title(:new, _), do: "New Note"
  def page_title(:create, _), do: "New Note"

  def page_title(:edit, _), do: "Edit Note"
  def page_title(:update, _), do: "Edit Note"

  def page_title(:show, assigns), do: assigns[:note].title <> " — Note #" <> Integer.to_string(assigns[:note].id)
end

defmodule Wwwtech.NoteView do
  use Wwwtech.Web, :view
end


Improve docs for Orddict.

Improve docs for Orddict.

defmodule Orddict do
  def from_list(pairs) do
    Erlang.lists.foldl fn({k, v}, dict){ store(dict, k, v) }, [], pairs
  end

  def from_enum(pairs) do
    Enum.foldl pairs, [], fn({k, v}, dict){ store(dict, k, v) }
  end

  # Gets value from the dictionary for specific key.
  # If key not exist return default value (nil if no default value)
  # exists.
  #
  # ## Examples
  #
  #     Orddict.get [a: 1], :a      #=> 1
  #     Orddict.get [a: 1], :b      #=> nil
  #     Orddict.get [a: 1], :b, 3   #=> 3
  #
  def get([{k, _}|_], key, default) when key < k, do: default
  def get([{k, _}|d], key, default) when key > k, do: get(d, key, default)
  def get([{_k, value}|_], _key, _default),       do: value
  def get([], _, default // nil),                 do: default

  # Returns all keys of dictionary.
  #
  # ## Examples
  #
  #     Orddict.keys [a: 1, b: 2] #=> [:a,:b]
  def keys(dict) do
    lc { key, _ } in dict, do: key
  end

  # Returns all values of dictionary.
  #
  # ## Examples
  #
  #     Orddict.values [a: 1, b: 2] #=> [1,2]
  def values(dict) do
    lc { _, value } in dict, do: value
  end

  # Deletes key, value entry from dictionary for specific key.
  # If the key does not exist, returns the dictionary unchanged.
  #
  # ## Examples
  #
  #     Orddict.delete [a: 1, b: 2], :a   #=> [b: 2]
  #     Orddict.delete [b: 2], :a         #=> [b: 2]
  #
  def delete([{k, _} = e|dict], key) when key < k, do: [e|dict]
  def delete([{k, _} = e|dict], key) when key > k, do: [e|delete(dict, key)]
  def delete([{_k, _v}|dict], _key), do: dict
  def delete([], _), do: []

  # Stores key, value entry in dictionary
  def store([{k, _} = e|dict], key, value) when key < k, do: [{key, value},e|dict]
  def store([{k, _} = e|dict], key, value) when key > k, do: [e|store(dict, key, value)]
  def store([{_, _}|dict], key, value), do: [{key, value}|dict]
  def store([], key, value), do: [{key, value}]

  # Merges two dictionaries in one
  def merge([{k1, _} = e1|d1], [{k2, _} = e2|d2]) when k1 < k2, do: [e1|merge(d1, [e2|d2])];
  def merge([{k1, _} = e1|d1], [{k2, _} = e2|d2]) when k1 > k2, do: [e2|merge([e1|d1], d2)];
  def merge([{k1, _v1}|d1], [{_k2, v2}|d2]), do: [{k1, v2}|merge(d1, d2)];
  def merge([], d2), do: d2
  def merge(d1, []), do: d1
end

defmodule Orddict do
  def from_list(pairs) do
    Erlang.lists.foldl fn({k, v}, dict){ store(dict, k, v) }, [], pairs
  end

  def from_enum(pairs) do
    Enum.foldl pairs, [], fn({k, v}, dict){ store(dict, k, v) }
  end

  # Fetches value from the dictionary for specific key.
  # If key not exist return default value
  def get([{k, _}|_], key, default) when key < k, do: default
  def get([{k, _}|d], key, default) when key > k, do: get(d, key, default)
  def get([{_k, value}|_], _key, _default),       do: value
  def get([], _, default // nil),                 do: default

  # Returns all keys of dictionary
  def keys([{k, _}|t]), do: [k|keys(t)]
  def keys([]),        do: []

  # Returns all values of dictionary
  def values([{_, v}|t]), do: [v|values(t)]
  def values([]),        do: []

  # Deletes key, value entry from dictionary for specific key
  def delete([{k, _} = e|dict], key) when key < k, do: [e|dict]
  def delete([{k, _} = e|dict], key) when key > k, do: [e|delete(dict, key)]
  def delete([{_k, _v}|dict], _key), do: dict
  def delete([], _), do: []

  # Stores key, value entry in dictionary
  def store([{k, _} = e|dict], key, value) when key < k, do: [{key, value},e|dict]
  def store([{k, _} = e|dict], key, value) when key > k, do: [e|store(dict, key, value)]
  def store([{_, _}|dict], key, value), do: [{key, value}|dict]
  def store([], key, value), do: [{key, value}]

  # Merges two dictionaries in one
  def merge([{k1, _} = e1|d1], [{k2, _} = e2|d2]) when k1 < k2, do: [e1|merge(d1, [e2|d2])];
  def merge([{k1, _} = e1|d1], [{k2, _} = e2|d2]) when k1 > k2, do: [e2|merge([e1|d1], d2)];
  def merge([{k1, _v1}|d1], [{_k2, v2}|d2]), do: [{k1, v2}|merge(d1, d2)];
  def merge([], d2), do: d2
  def merge(d1, []), do: d1
end


Refactor for performance

Refactor for performance

defmodule Jsox.Parser do
  @moduledoc """
  A JSON parser according to ECMA-404.

  Standard ECMA-404: The JSON Data Interchange Format
  https://www.ecma-international.org/publications/standards/Ecma-404.htm
  """

  use Bitwise

  @type json :: map | list | String.t | integer | float | true | false | nil

  @digits '0123456789'
  @minus_sign ?-
  @full_stop ?.
  @exps 'eE'
  @exp ?e
  @quotation_mark ?"
  @escape ?\\
  @escapes %{
    ?\\ => '\\',
    ?" => '\"',
    ?n => '\n',
    ?r => '\r',
    ?b => '\b',
    ?f => '\f',
    ?t => '\t'
  }
  @escape_chars Map.keys(@escapes)
  @solidus ?/
  @unicode ?u
  @whitespace '\s\r\t\n'
  @surrogate_a 'dD'
  @surrogate_b1 '89abAb'
  @surrogate_b2 'cedfCDEF'
  @left_square_bracket ?[
  @right_square_bracket ?]
  @left_curly_bracket ?{
  @right_curly_bracket ?}
  @colon ?:
  @comma ?,

  @spec parse(iodata) :: {:ok, json} | {:error, String.t}
  def parse(iodata) do
    {result, iodata, pos} = parse_json(iodata, 0)
    if iodata =~ ~r/^\s*$/ do
      {:ok, result}
    else
      {:error, :eof, pos}
    end
  catch
    {token, pos} -> {:error, token, pos}
  end

  defp parse_json(<<char>> <> iodata, pos) do
    pos = pos + 1
    cond do
      char in @whitespace -> parse_json(iodata, pos)
      char == @quotation_mark -> parse_string(iodata, pos, [])
      char == @left_square_bracket -> parse_list(iodata, pos, [])
      char == @left_curly_bracket-> parse_map(iodata, pos, [])
      char == @minus_sign or char in @digits -> parse_number(iodata, pos, [char])
      char == ?t -> parse_true(iodata, pos)
      char == ?f -> parse_false(iodata, pos)
      char == ?n -> parse_null(iodata, pos)
      true -> throw {:json, pos}
    end
  end

  defp parse_true(<<"rue">> <> iodata, pos), do: {true, iodata, pos + 3}
  defp parse_true(_iodata, pos), do: throw {:true, pos}

  defp parse_false(<<"alse">> <> iodata, pos), do: {false, iodata, pos + 4}
  defp parse_false(_iodata, pos), do: throw {:false, pos}

  defp parse_null(<<"ull">> <> iodata, pos), do: {nil, iodata, pos + 3}
  defp parse_null(_iodata, pos), do: throw {:null, pos}

  defp parse_key(<<char>> <> iodata, pos) do
    pos = pos + 1
    cond do
      char in @whitespace -> parse_key(iodata, pos)
      char == @quotation_mark -> parse_string(iodata, pos, [])
      true -> throw {:key, pos}
    end
  end

  defp parse_number(<<char>> <> iodata, pos, chars)
    when char in @digits,
    do: parse_number(iodata, pos + 1, [char|chars])
  defp parse_number(<<@full_stop>> <> _iodata, pos, [@minus_sign]),
    do: throw {:number, pos}
  defp parse_number(<<@full_stop>> <> iodata, pos, chars),
    do: parse_float(iodata, pos + 1, [@full_stop|chars])
  defp parse_number(<<char>> <> _iodata, pos, [@minus_sign])
    when char in @exps,
    do: throw {:number, pos}
  defp parse_number(<<char>> <> iodata, pos, chars)
    when char in @exps,
    do: parse_exponential(iodata, pos + 1, [@exp|['.0'|chars]])
  defp parse_number(_iodata, pos, [@minus_sign]),
    do: throw {:number, pos}
  defp parse_number(iodata, pos, chars) do
    result = chars
             |> Enum.reverse
             |> IO.iodata_to_binary
             |> String.to_integer
    {result, iodata, pos}
  end

  defp parse_exponential(<<@minus_sign>> <> iodata, pos, [@exp|_] = chars),
    do: parse_exponential(iodata, pos + 1, [@minus_sign|chars])
  defp parse_exponential(<<char>> <> iodata, pos, chars)
    when char in @digits,
    do: parse_exponential(iodata, pos + 1, [char|chars])
  defp parse_exponential(_iodata, pos, [@exp|_]),
    do: throw {:exponential, pos}
  defp parse_exponential(_iodata, pos, [@minus_sign|_]),
    do: throw {:exponential, pos}
  defp parse_exponential(iodata, pos, chars) do
    result = chars
        |> Enum.reverse
        |> IO.iodata_to_binary
        |> String.to_float
    {result, iodata, pos}
  end

  defp parse_float(<<char>> <> _iodata, pos, [@full_stop|_])
    when char in @exps,
    do: throw {:float, pos}
  defp parse_float(<<char>> <> iodata, pos, chars)
    when char in @exps,
    do: parse_exponential(iodata, pos + 1, [@exp|chars])
  defp parse_float(<<char>> <> iodata, pos, chars)
    when char in @digits,
    do: parse_float(iodata, pos + 1, [char|chars])
  defp parse_float(iodata, pos, chars) do
    result = chars
             |> Enum.reverse
             |> IO.iodata_to_binary
             |> String.to_float
    {result, iodata, pos}
  end

  defp parse_string(<<@quotation_mark>> <> iodata, pos, chars) do
    result = chars
             |> Enum.reverse
             |> IO.iodata_to_binary
    {result, iodata, pos}
  end
  defp parse_string(<<@escape, @solidus>> <> iodata, pos, chars),
    do: parse_string(iodata, pos + 2, [@solidus|chars])
  defp parse_string(<<@escape, char>> <> iodata, pos, chars)
    when char in @escape_chars,
    do: parse_string(iodata, pos + 2, [Map.get(@escapes, char)|chars])
  defp parse_string(
             <<@escape, @unicode, a1, b1, c1, d1, @escape, @unicode, a2, b2, c2, d2>> <> iodata,
             pos, chars)
    when a1 in @surrogate_a and a2 in @surrogate_a and b1 in @surrogate_b1 and b2 in @surrogate_b2 do
    hi = List.to_integer([a1, b1, c1, d1], 16)
    lo = List.to_integer([a2, b2, c2, d2], 16)
    codepoint = 0x10000 + ((hi &&& 0x03FF) <<< 10) + (lo &&& 0x03FF)
    parse_string(iodata, pos + 11, [<<codepoint :: utf8>>|chars])
  end
  defp parse_string(<<@escape, @unicode, seq :: binary-size(4)>> <> iodata, pos, chars),
    do: parse_string(iodata, pos + 6, [<<String.to_integer(seq, 16) :: utf8>>|chars])
  defp parse_string(<<@escape>> <> _iodata, pos, _chars),
    do: throw {:string, pos}
  defp parse_string(<<char>> <> iodata, pos, chars),
    do: parse_string(iodata, pos + 1, [char|chars])

  defp parse_list(<<char>> <> iodata, pos, list)
    when char in @whitespace,
    do: parse_list(iodata, pos + 1, list)
  defp parse_list(<<@right_square_bracket>> <> iodata, pos, list),
    do: {Enum.reverse(list), iodata, pos + 1}
  defp parse_list(<<@comma>> <> _iodata, pos, []),
    do: throw {:list, pos}
  defp parse_list(<<@comma>> <> iodata, pos, list) do
    {result, iodata, pos} = parse_json(iodata, pos + 1)
    parse_list(iodata, pos, [result|list])
  end
  defp parse_list(iodata, pos, []) do
    {result, iodata, pos} = parse_json(iodata, pos)
    parse_list(iodata, pos, [result])
  end
  defp parse_list(_iodata, pos, _list),
    do: throw{:list, pos}

  defp parse_map(<<char>> <> iodata, pos, list)
    when char in @whitespace,
    do: parse_map(iodata,  pos + 1, list)
  defp parse_map(<<@right_curly_bracket>> <> iodata, pos, list) do
    result = for [value, key] <- Enum.chunk(list, 2), into: %{}, do: {key, value}
    {result, iodata, pos + 1}
  end
  defp parse_map(<<@comma>> <> _iodata, pos, []),
    do: throw {:map, pos + 1}
  defp parse_map(<<@comma>> <> iodata, pos, list) do
    {result, iodata, pos} = parse_key(iodata, pos + 1)
    parse_map(iodata, pos + 1, [result|list])
  end
  defp parse_map(<<@colon>> <> _iodata, pos, []),
    do: throw {:map, pos + 1}
  defp parse_map(<<@colon>> <> iodata, pos, list) do
    {result, iodata, pos} = parse_json(iodata, pos + 1)
    parse_map(iodata, pos + 1, [result|list])
  end
  defp parse_map(iodata, pos, []) do
    {result, iodata, pos} = parse_key(iodata, pos)
    parse_map(iodata, pos, [result])
  end
  defp parse_map(_iodata, pos, _list),
    do: throw {:map, pos + 1}

end

defmodule Jsox.Parser do
  @moduledoc """
  A JSON parser according to ECMA-404.

  Standard ECMA-404: The JSON Data Interchange Format
  https://www.ecma-international.org/publications/standards/Ecma-404.htm
  """

  use Bitwise

  @type json :: map | list | String.t | integer | float | true | false | nil

  @digits '0123456789'
  @minus_sign ?-
  @full_stop ?.
  @exps 'eE'
  @exp ?e
  @quotation_mark ?"
  @escape ?\\
  @escapes %{
    ?\\ => '\\',
    ?" => '\"',
    ?n => '\n',
    ?r => '\r',
    ?b => '\b',
    ?f => '\f',
    ?t => '\t'
  }
  @escape_chars Map.keys(@escapes)
  @solidus ?/
  @unicode ?u
  @whitespace '\s\r\t\n'
  @surrogate_a 'dD'
  @surrogate_b1 '89abAb'
  @surrogate_b2 'cedfCDEF'
  @left_square_bracket ?[
  @right_square_bracket ?]
  @left_curly_bracket ?{
  @right_curly_bracket ?}
  @colon ?:
  @comma ?,
  @json_true "true"
  @json_false "false"
  @json_null "null"

  @spec parse(iodata) :: {:ok, json} | {:error, String.t}
  def parse(iodata) do
    {result, iodata, pos} = parse_json(iodata, 0)
    if iodata =~ ~r/^\s*$/ do
      {:ok, result}
    else
      {:error, :eof, pos}
    end
  catch
    {token, pos} -> {:error, token, pos}
  end

  defp parse_json(<<char>> <> iodata, pos)
    when char in @whitespace,
    do: parse_json(iodata, pos + 1)
  defp parse_json(<<char>> <> iodata, pos)
    when char == @minus_sign or char in @digits,
    do: parse_number(iodata, pos + 1, [char])
  defp parse_json(<<@quotation_mark>> <> iodata, pos),
    do: parse_string(iodata, pos + 1, [])
  defp parse_json(<<@left_square_bracket>> <> iodata, pos),
    do: parse_list(iodata, pos + 1, [])
  defp parse_json(<<@left_curly_bracket>> <> iodata, pos),
    do: parse_map(iodata, pos + 1, [])
  defp parse_json(<<@json_true>> <> iodata, pos),
    do: {true, iodata, pos + 4}
  defp parse_json(<<@json_false>> <> iodata, pos),
    do: {false, iodata, pos + 5}
  defp parse_json(<<@json_null>> <> iodata, pos),
    do: {nil, iodata, pos + 5}
  defp parse_json(_iodata, pos),
    do: throw {:json, pos + 1}

  defp parse_key(<<char>> <> iodata, pos)
    when char in @whitespace,
    do: parse_key(iodata, pos + 1)
  defp parse_key(<<char>> <> iodata, pos)
    when char == @quotation_mark,
    do: parse_string(iodata, pos + 1, [])
  defp parse_key(_iodata, pos),
    do: throw {:key, pos + 1}

  defp parse_number(<<char>> <> iodata, pos, chars)
    when char in @digits,
    do: parse_number(iodata, pos + 1, [char|chars])
  defp parse_number(<<@full_stop>> <> _iodata, pos, [@minus_sign]),
    do: throw {:number, pos}
  defp parse_number(<<@full_stop>> <> iodata, pos, chars),
    do: parse_float(iodata, pos + 1, [@full_stop|chars])
  defp parse_number(<<char>> <> _iodata, pos, [@minus_sign])
    when char in @exps,
    do: throw {:number, pos}
  defp parse_number(<<char>> <> iodata, pos, chars)
    when char in @exps,
    do: parse_exponential(iodata, pos + 1, [@exp|['.0'|chars]])
  defp parse_number(_iodata, pos, [@minus_sign]),
    do: throw {:number, pos}
  defp parse_number(iodata, pos, chars) do
    result = chars
             |> Enum.reverse
             |> IO.iodata_to_binary
             |> String.to_integer
    {result, iodata, pos}
  end

  defp parse_exponential(<<@minus_sign>> <> iodata, pos, [@exp|_] = chars),
    do: parse_exponential(iodata, pos + 1, [@minus_sign|chars])
  defp parse_exponential(<<char>> <> iodata, pos, chars)
    when char in @digits,
    do: parse_exponential(iodata, pos + 1, [char|chars])
  defp parse_exponential(_iodata, pos, [@exp|_]),
    do: throw {:exponential, pos}
  defp parse_exponential(_iodata, pos, [@minus_sign|_]),
    do: throw {:exponential, pos}
  defp parse_exponential(iodata, pos, chars) do
    result = chars
        |> Enum.reverse
        |> IO.iodata_to_binary
        |> String.to_float
    {result, iodata, pos}
  end

  defp parse_float(<<char>> <> _iodata, pos, [@full_stop|_])
    when char in @exps,
    do: throw {:float, pos}
  defp parse_float(<<char>> <> iodata, pos, chars)
    when char in @exps,
    do: parse_exponential(iodata, pos + 1, [@exp|chars])
  defp parse_float(<<char>> <> iodata, pos, chars)
    when char in @digits,
    do: parse_float(iodata, pos + 1, [char|chars])
  defp parse_float(iodata, pos, chars) do
    result = chars
             |> Enum.reverse
             |> IO.iodata_to_binary
             |> String.to_float
    {result, iodata, pos}
  end

  defp parse_string(<<@quotation_mark>> <> iodata, pos, chars) do
    result = chars
             |> Enum.reverse
             |> IO.iodata_to_binary
    {result, iodata, pos}
  end
  defp parse_string(<<@escape, @solidus>> <> iodata, pos, chars),
    do: parse_string(iodata, pos + 2, [@solidus|chars])
  defp parse_string(<<@escape, char>> <> iodata, pos, chars)
    when char in @escape_chars,
    do: parse_string(iodata, pos + 2, [Map.get(@escapes, char)|chars])
  defp parse_string(
             <<@escape, @unicode, a1, b1, c1, d1, @escape, @unicode, a2, b2, c2, d2>> <> iodata,
             pos, chars)
    when a1 in @surrogate_a and a2 in @surrogate_a and b1 in @surrogate_b1 and b2 in @surrogate_b2 do
    hi = List.to_integer([a1, b1, c1, d1], 16)
    lo = List.to_integer([a2, b2, c2, d2], 16)
    codepoint = 0x10000 + ((hi &&& 0x03FF) <<< 10) + (lo &&& 0x03FF)
    parse_string(iodata, pos + 11, [<<codepoint :: utf8>>|chars])
  end
  defp parse_string(<<@escape, @unicode, seq :: binary-size(4)>> <> iodata, pos, chars),
    do: parse_string(iodata, pos + 6, [<<String.to_integer(seq, 16) :: utf8>>|chars])
  defp parse_string(<<@escape>> <> _iodata, pos, _chars),
    do: throw {:string, pos}
  defp parse_string(<<char>> <> iodata, pos, chars),
    do: parse_string(iodata, pos + 1, [char|chars])

  defp parse_list(<<char>> <> iodata, pos, list)
    when char in @whitespace,
    do: parse_list(iodata, pos + 1, list)
  defp parse_list(<<@right_square_bracket>> <> iodata, pos, list),
    do: {Enum.reverse(list), iodata, pos + 1}
  defp parse_list(<<@comma>> <> _iodata, pos, []),
    do: throw {:list, pos}
  defp parse_list(<<@comma>> <> iodata, pos, list) do
    {result, iodata, pos} = parse_json(iodata, pos + 1)
    parse_list(iodata, pos, [result|list])
  end
  defp parse_list(iodata, pos, []) do
    {result, iodata, pos} = parse_json(iodata, pos)
    parse_list(iodata, pos, [result])
  end
  defp parse_list(_iodata, pos, _list),
    do: throw{:list, pos}

  defp parse_map(<<char>> <> iodata, pos, list)
    when char in @whitespace,
    do: parse_map(iodata,  pos + 1, list)
  defp parse_map(<<@right_curly_bracket>> <> iodata, pos, list) do
    result = for [value, key] <- Enum.chunk(list, 2), into: %{}, do: {key, value}
    {result, iodata, pos + 1}
  end
  defp parse_map(<<@comma>> <> _iodata, pos, []),
    do: throw {:map, pos + 1}
  defp parse_map(<<@comma>> <> iodata, pos, list) do
    {result, iodata, pos} = parse_key(iodata, pos + 1)
    parse_map(iodata, pos + 1, [result|list])
  end
  defp parse_map(<<@colon>> <> _iodata, pos, []),
    do: throw {:map, pos + 1}
  defp parse_map(<<@colon>> <> iodata, pos, list) do
    {result, iodata, pos} = parse_json(iodata, pos + 1)
    parse_map(iodata, pos + 1, [result|list])
  end
  defp parse_map(iodata, pos, []) do
    {result, iodata, pos} = parse_key(iodata, pos)
    parse_map(iodata, pos, [result])
  end
  defp parse_map(_iodata, pos, _list),
    do: throw {:map, pos + 1}

end


Remove unused methods

Remove unused methods

defmodule Mnesia.RecordNotFoundError do
  defexception [:table, :id]

  def message(%{table: table, id: id}),
    do: "Couldn't find #{inspect table} with id=#{id}"
end

defmodule Mnesia.Repo do
  @moduledoc """
  Thin wrapper between Ecto and Mnesia, allows Ecto.Changeset's to be used in
  conjunction with ETS.
  """

  @callback fields() :: [atom]

  def save(%Ecto.Changeset{} = changeset) do
    changeset
    |> Ecto.Changeset.apply_changes()
    |> save()
  end

  def save(struct) do
    module = struct.__struct__

    struct = if module.should_generate_primary_key?(struct),
      do: module.generate_primary_key(struct),
      else: struct

    {created_at, updated_at} = module.timestamps()

    struct = if Map.has_key?(struct, created_at) do
      Map.update(struct, created_at, nil, &keep_timestamp/1)
    else
      struct
    end

    struct = if Map.has_key?(struct, updated_at) do
      Map.update(struct, updated_at, nil, &put_timestamp/1)
    else
      struct
    end

    write = fn ->
      :mnesia.write(module.record(struct))
    end

    {:atomic, :ok} = :mnesia.transaction(write)

    struct
  end

  def reload(struct) do
    struct
    |> struct.__struct__.get_primary_key
    |> struct.__struct__.get
  end

  def dirty_find!(tab, id) do
    case dirty_find(tab, id) do
      {:ok, r} -> r
      {:error, e} -> raise e
    end
  end

  def dirty_find(tab, id) do
    with [record] <- :mnesia.dirty_read(tab, id) do
      {:ok,
       Mnesia.Repo.load(record)}
    else
      [] ->
        {:error,
         %Mnesia.RecordNotFoundError{id: id, table: tab}}
    end
  end

  def load(record) when is_tuple(record) do
    [table_name |attrs] = Tuple.to_list(record)
    attrs = Enum.zip(table_name.fields, attrs)
    struct(table_name, attrs)
  end

  def load(l, acc \\ [])

  def load([], acc) do
    acc
  end

  def load([h|t], acc) do
    load(t, [load(h)|acc])
  end

  def all(module) when is_atom(module) do
    fn ->
      :qlc.e(:mnesia.table module.table_name)
    end
    |> :mnesia.async_dirty()
    |> Enum.map(&module.load/1)
  end

  defp keep_timestamp(nil), do: System.monotonic_time()
  defp keep_timestamp(time), do: time
  defp put_timestamp(_), do: System.monotonic_time()

  defmacro __using__(_) do
    quote do
      use Ecto.Schema

      @behaviour unquote(__MODULE__)
      @primary_key {:id, :id, autogenerate: true}
      @timestamps {:created_at, :updated_at}

      def timestamps() do
        @timestamps
      end

      def table, do: [attributes: fields()]

      def record(struct) do
        get = &Map.get(struct, &1)
        attrs = Enum.map(fields(), get)
        List.to_tuple [table_name() |attrs]
      end

      def load(record) do
        [mod |attrs] = Tuple.to_list(record)
        attrs = Enum.zip(mod.fields(), attrs)
        struct(mod, attrs)
      end

      def fields do
        __schema__(:fields)
      end

      def all() do
        Mnesia.Repo.all(__MODULE__)
      end

      def last do
        load(:mnesia.last(table_name()))
      end

      def get(id) do
        read = fn ->
          :mnesia.read(table_name(), id)
        end

        with {:atomic, [record]} <- :mnesia.transaction(read) do
          {:ok, Mnesia.Repo.load(record)}
        else
          {:atomic, []} ->
            {:error, %Mnesia.RecordNotFoundError{id: id, table: table_name()}}
        end
      end

      @spec create_table(Keyword.t) :: {:atomic, :ok} | {:aborted, any}
      def create_table(opts \\ []) do
        :mnesia.create_table(table_name(), opts ++ table())
      end

      def delete_table do
        :mnesia.delete_table(table_name())
      end

      def change_table_copy_type(node, type) do
        :mnesia.change_table_copy_type(table_name(), node, type)
      end

      def delete_all do
        :mnesia.clear_table(table_name())
      end

      def get_primary_key(struct) do
        Map.fetch!(struct, primary_key_field())
      end

      defp primary_key_field() do
        {field, _, _} = @primary_key
        field
      end

      defp primary_key_opts() do
        {_, _, opts} = @primary_key
        opts
      end

      def generate_primary_key(%{__struct__: __MODULE__} = struct) do
        Map.put(struct, primary_key_field(), Ecto.UUID.generate())
      end

      def should_generate_primary_key?(%{__struct__: __MODULE__} = struct) do
        get_primary_key(struct) == nil and
        {:ok, true} == Keyword.fetch(primary_key_opts(), :autogenerate)
      end

      defoverridable [fields: 0]
    end
  end
end

defmodule Mnesia.RecordNotFoundError do
  defexception [:table, :id]

  def message(%{table: table, id: id}),
    do: "Couldn't find #{inspect table} with id=#{id}"
end

defmodule Mnesia.Repo do
  @moduledoc """
  Thin wrapper between Ecto and Mnesia, allows Ecto.Changeset's to be used in
  conjunction with ETS.
  """

  @callback fields() :: [atom]

  def save(%Ecto.Changeset{} = changeset) do
    changeset
    |> Ecto.Changeset.apply_changes()
    |> save()
  end

  def save(struct) do
    module = struct.__struct__

    struct = if module.should_generate_primary_key?(struct),
      do: module.generate_primary_key(struct),
      else: struct

    {created_at, updated_at} = module.timestamps()

    struct = if Map.has_key?(struct, created_at) do
      Map.update(struct, created_at, nil, &keep_timestamp/1)
    else
      struct
    end

    struct = if Map.has_key?(struct, updated_at) do
      Map.update(struct, updated_at, nil, &put_timestamp/1)
    else
      struct
    end

    write = fn ->
      :mnesia.write(module.record(struct))
    end

    {:atomic, :ok} = :mnesia.transaction(write)

    struct
  end

  def reload(struct) do
    struct
    |> struct.__struct__.get_primary_key
    |> struct.__struct__.get
  end

  def dirty_find!(tab, id) do
    case dirty_find(tab, id) do
      {:ok, r} -> r
      {:error, e} -> raise e
    end
  end

  def dirty_find(tab, id) do
    with [record] <- :mnesia.dirty_read(tab, id) do
      {:ok,
       Mnesia.Repo.load(record)}
    else
      [] ->
        {:error,
         %Mnesia.RecordNotFoundError{id: id, table: tab}}
    end
  end

  def load(record) when is_tuple(record) do
    [table_name |attrs] = Tuple.to_list(record)
    attrs = Enum.zip(table_name.fields, attrs)
    struct(table_name, attrs)
  end

  def load(l, acc \\ [])

  def load([], acc) do
    acc
  end

  def load([h|t], acc) do
    load(t, [load(h)|acc])
  end

  def all(module) when is_atom(module) do
    fn ->
      :qlc.e(:mnesia.table module.table_name)
    end
    |> :mnesia.async_dirty()
    |> Enum.map(&module.load/1)
  end

  defp keep_timestamp(nil), do: System.monotonic_time()
  defp keep_timestamp(time), do: time
  defp put_timestamp(_), do: System.monotonic_time()

  defmacro __using__(_) do
    quote do
      use Ecto.Schema

      @behaviour unquote(__MODULE__)
      @primary_key {:id, :id, autogenerate: true}
      @timestamps {:created_at, :updated_at}

      def timestamps() do
        @timestamps
      end

      def table, do: [attributes: fields()]

      def record(struct) do
        get = &Map.get(struct, &1)
        attrs = Enum.map(fields(), get)
        List.to_tuple [table_name() |attrs]
      end

      def load(record) do
        [mod |attrs] = Tuple.to_list(record)
        attrs = Enum.zip(mod.fields(), attrs)
        struct(mod, attrs)
      end

      def fields do
        __schema__(:fields)
      end

      def all() do
        Mnesia.Repo.all(__MODULE__)
      end

      def last do
        load(:mnesia.last(table_name()))
      end

      def get(id) do
        read = fn ->
          :mnesia.read(table_name(), id)
        end

        with {:atomic, [record]} <- :mnesia.transaction(read) do
          {:ok, Mnesia.Repo.load(record)}
        else
          {:atomic, []} ->
            {:error, %Mnesia.RecordNotFoundError{id: id, table: table_name()}}
        end
      end

      @spec create_table(Keyword.t) :: {:atomic, :ok} | {:aborted, any}
      def create_table(opts \\ []) do
        :mnesia.create_table(table_name(), opts ++ table())
      end

      def delete_table do
        :mnesia.delete_table(table_name())
      end

      def change_table_copy_type(node, type) do
        :mnesia.change_table_copy_type(table_name(), node, type)
      end

      def delete_all do
        :mnesia.clear_table(table_name())
      end

      def get_primary_key(struct) do
        Map.fetch!(struct, primary_key_field())
      end

      defp primary_key_field() do
        {field, _, _} = @primary_key
        field
      end

      defp primary_key_opts() do
        {_, _, opts} = @primary_key
        opts
      end

      def generate_primary_key(%{__struct__: __MODULE__} = struct) do
        Map.put(struct, primary_key_field(), Ecto.UUID.generate())
      end

      def should_generate_primary_key?(%{__struct__: __MODULE__} = struct) do
        get_primary_key(struct) == nil and
        {:ok, true} == Keyword.fetch(primary_key_opts(), :autogenerate)
      end

      defp keep_timestamp(nil), do: System.monotonic_time()
      defp keep_timestamp(time), do: time

      defp put_timestamp(_), do: System.monotonic_time()

      defoverridable [fields: 0]
    end
  end
end


Add test for not scaling down below ones

defmodule Benchee.Formatters.UnitsTest do
  use ExUnit.Case
  import Benchee.Units

  test ".format 123_456_789_012 scales to :billion" do
    assert scale_count(123_456_789_012) == {123.456789012, :billion}
  end

  test ".format 12_345_678_901 scales to :billion" do
    assert scale_count(12_345_678_901) == {12.345678901, :billion}
  end

  test ".format 1_234_567_890 scales to :billion" do
    assert scale_count(1_234_567_890) == {1.23456789, :billion}
  end

  test ".format 123_456_789 scales to :million" do
    assert scale_count(123_456_789) == {123.456789, :million}
  end

  test ".format 12_345_678 scales to :million" do
    assert scale_count(12_345_678) == {12.345678, :million}
  end

  test ".format 1_234_567 scales to :million" do
    assert scale_count(1_234_567) == {1.234567, :million}
  end

  test ".format 123_456.7 scales to :thousand" do
    assert scale_count(123_456.7) == {123.4567, :thousand}
  end

  test ".format 12_345.67 scales to :thousand" do
    assert scale_count(12_345.67) == {12.34567, :thousand}
  end

  test ".format 1_234.567 scales to :thousand" do
    assert scale_count(1_234.567) == {1.234567, :thousand}
  end

  test ".format 123.4567 scales to :one" do
    assert scale_count(123.4567) == {123.4567, :one}
  end

  test ".format 12.34567 scales to :one" do
    assert scale_count(12.34567) == {12.34567, :one}
  end

  test ".format 1.234567 scales to :one" do
    assert scale_count(1.234567) == {1.234567, :one}
  end

  test ".format 0.001234567 scales to :one" do
    assert scale_count(0.001234567) == {0.001234567, :one}
  end
end

defmodule Benchee.Formatters.UnitsTest do
  use ExUnit.Case
  import Benchee.Units

  test ".format 123_456_789_012 scales to :billion" do
    assert scale_count(123_456_789_012) == {123.456789012, :billion}
  end

  test ".format 12_345_678_901 scales to :billion" do
    assert scale_count(12_345_678_901) == {12.345678901, :billion}
  end

  test ".format 1_234_567_890 scales to :billion" do
    assert scale_count(1_234_567_890) == {1.23456789, :billion}
  end

  test ".format 123_456_789 scales to :million" do
    assert scale_count(123_456_789) == {123.456789, :million}
  end

  test ".format 12_345_678 scales to :million" do
    assert scale_count(12_345_678) == {12.345678, :million}
  end

  test ".format 1_234_567 scales to :million" do
    assert scale_count(1_234_567) == {1.234567, :million}
  end

  test ".format 123_456.7 scales to :thousand" do
    assert scale_count(123_456.7) == {123.4567, :thousand}
  end

  test ".format 12_345.67 scales to :thousand" do
    assert scale_count(12_345.67) == {12.34567, :thousand}
  end

  test ".format 1_234.567 scales to :thousand" do
    assert scale_count(1_234.567) == {1.234567, :thousand}
  end

  test ".format 123.4567 scales to :one" do
    assert scale_count(123.4567) == {123.4567, :one}
  end

  test ".format 12.34567 scales to :one" do
    assert scale_count(12.34567) == {12.34567, :one}
  end

  test ".format 1.234567 scales to :one" do
    assert scale_count(1.234567) == {1.234567, :one}
  end
end


Starts the coordinator processes as environment and uses the correct process names.

defmodule Baumeister.ObserverTest do
  use ExUnit.Case

  require Logger
  @moduletag capture_log: true

  alias Baumeister.Test.TestListener
  alias Baumeister.Test.Utils
  alias Experimental.GenStage
  alias Baumeister.EventCenter

  alias Baumeister.Observer
  alias Baumeister.Observer.FailPlugin
  alias Baumeister.Observer.NoopPlugin
  alias Baumeister.Observer.Take
  alias Baumeister.Observer.Delay

  setup context do
    IO.inspect(context)
    sup_name = Baumeister.Supervisor
    opts = [strategy: :one_for_one, name: sup_name]
    children = Baumeister.App.setup_coordinator()
    if (GenServer.whereis(sup_name) != nil), do: :ok = Supervisor.stop(sup_name)
    {:ok, sup_pid} = Supervisor.start_link(children, opts)
    counts = Supervisor.count_children(sup_pid)
    assert counts[:specs] == counts[:active]

    # need process flag, because Observer will crash
    Process.flag(:trap_exit, true)
    {:ok, listener} = TestListener.start()
    GenStage.sync_subscribe(listener, to: Baumeister.EventCenter.name())
    # set the observer name to the test name
    {:ok, pid} = Observer.start_link(context[:test] |> Atom.to_string())
    assert is_pid(pid)

    # Let the listener drain the event queue of old events.
    Utils.wait_for fn -> 0 == EventCenter.clear() end
    # wait_for fn -> 0 == TestListener.clear(listener) end

    # merge this with the context
    [pid: pid, listener: listener]
  end

  @tag timeout: 1_000
  test "observe with a failing plugin", context do
    pid = context[:pid]
    listener = context[:listener]

    Observer.configure(pid, FailPlugin, :ok)
    :ok = Observer.run(pid)

    Utils.wait_for fn -> length(TestListener.get(listener)) >= 3 end

    l = TestListener.get(listener) |> Enum.take(3)
    assert length(l) == 3
    assert [{_, :start_observer, _}, {_, :exec_observer, _},
        {_, :failed_observer, _}] = l

    # It should really die, otherwise it's not implemented as it should
    Logger.debug "Test Observer"
    refute Process.alive?(pid)
  end

  @tag timeout: 1_000
  test "take and noop", context do
    pid = context[:pid]
    listener = context[:listener]
    name = context[:test] |> Atom.to_string()
    bmf = """
    command: echo "Ja, wir schaffen das"
    """

    Observer.configure(pid, [{NoopPlugin, {"file:///", bmf}},{Take, 2}])
    TestListener.clear(listener)
    :ok = Observer.run(pid)

    Utils.wait_for fn -> length(TestListener.get(listener)) >= 5 end
    l = TestListener.get(listener)

    plug_events = l
    |> IO.inspect()
    |> Enum.filter(fn {_, _, n} -> n == name end)
    |> IO.inspect()
    |> Enum.map(fn {_, action, _} -> action end)
    assert length(plug_events) >= 5
    assert [:start_observer,
      :exec_observer, :exec_observer, :exec_observer,
      :stopped_observer] = plug_events
  end

  @tag timeout: 1_000
  test "take and noop - premature end", context do
    pid = context[:pid]
    listener = context[:listener]
    bmf = """
    command: echo "Ja, wir schaffen das"
    """

    Observer.configure(pid, [{Take, 0}, {NoopPlugin, {"file:///", bmf}}])

    :ok = Observer.run(pid)

    Utils.wait_for fn -> length(TestListener.get(listener)) >= 3 end

    l = TestListener.get(listener) |> Enum.take(3)
    assert length(l) == 3
    assert [{_, :start_observer, _}, {_, :exec_observer, _}, {_, :stopped_observer, _}] = l
  end

  @tag timeout: 1_000
  test "noop", context do
    pid = context[:pid]
    listener = context[:listener]
    bmf = """
    command: echo "Ja, wir schaffen das"
    """

    Observer.configure(pid, [{NoopPlugin, {"file:///", bmf}}, {Delay, 50}])
    TestListener.clear(listener)
    :ok = Observer.run(pid)

    Utils.wait_for fn -> length(TestListener.get(listener)) >= 2 end
    Observer.stop(pid, :stop)

    # take only the first two elements, since noop is extremely fast
    # and produces a huge amount of events.
    l = TestListener.get(listener) |> Enum.take(2)
    assert [{_, :start_observer, _}, {_, :exec_observer, _}] = l
  end

end

defmodule Baumeister.ObserverTest do
  use ExUnit.Case

  require Logger
  @moduletag capture_log: true

  alias Baumeister.Test.TestListener
  alias Baumeister.Test.Utils
  alias Experimental.GenStage
  alias Baumeister.EventCenter

  alias Baumeister.Observer
  alias Baumeister.Observer.FailPlugin
  alias Baumeister.Observer.NoopPlugin
  alias Baumeister.Observer.Take
  alias Baumeister.Observer.Delay

  setup context do
    IO.inspect(context)
    # need process flag, because Observer will crash
    Process.flag(:trap_exit, true)
    {:ok, listener} = TestListener.start()
    GenStage.sync_subscribe(listener, to: Baumeister.EventCenter)
    # set the observer name to the test name
    {:ok, pid} = Observer.start_link(context[:test] |> Atom.to_string())
    assert is_pid(pid)

    # Let the listener drain the event queue of old events.
    Utils.wait_for fn -> 0 == EventCenter.clear() end
    # wait_for fn -> 0 == TestListener.clear(listener) end

    # merge this with the context
    [pid: pid, listener: listener]
  end

  @tag timeout: 1_000
  test "observe with a failing plugin", context do
    pid = context[:pid]
    listener = context[:listener]

    Observer.configure(pid, FailPlugin, :ok)
    :ok = Observer.run(pid)

    Utils.wait_for fn -> length(TestListener.get(listener)) >= 3 end

    l = TestListener.get(listener) |> Enum.take(3)
    assert length(l) == 3
    assert [{_, :start_observer, _}, {_, :exec_observer, _},
        {_, :failed_observer, _}] = l

    # It should really die, otherwise it's not implemented as it should
    Logger.debug "Test Observer"
    refute Process.alive?(pid)
  end

  @tag timeout: 1_000
  test "take and noop", context do
    pid = context[:pid]
    listener = context[:listener]
    name = context[:test] |> Atom.to_string()
    bmf = """
    command: echo "Ja, wir schaffen das"
    """

    Observer.configure(pid, [{NoopPlugin, {"file:///", bmf}},{Take, 2}])
    TestListener.clear(listener)
    :ok = Observer.run(pid)

    Utils.wait_for fn -> length(TestListener.get(listener)) >= 5 end
    l = TestListener.get(listener)

    plug_events = l
    |> IO.inspect()
    |> Enum.filter(fn {_, _, n} -> n == name end)
    |> IO.inspect()
    |> Enum.map(fn {_, action, _} -> action end)
    assert length(plug_events) >= 5
    assert [:start_observer,
      :exec_observer, :exec_observer, :exec_observer,
      :stopped_observer] = plug_events
  end

  @tag timeout: 1_000
  test "take and noop - premature end", context do
    pid = context[:pid]
    listener = context[:listener]
    bmf = """
    command: echo "Ja, wir schaffen das"
    """

    Observer.configure(pid, [{Take, 0}, {NoopPlugin, {"file:///", bmf}}])

    :ok = Observer.run(pid)

    Utils.wait_for fn -> length(TestListener.get(listener)) >= 3 end

    l = TestListener.get(listener) |> Enum.take(3)
    assert length(l) == 3
    assert [{_, :start_observer, _}, {_, :exec_observer, _}, {_, :stopped_observer, _}] = l
  end

  @tag timeout: 1_000
  test "noop", context do
    pid = context[:pid]
    listener = context[:listener]
    bmf = """
    command: echo "Ja, wir schaffen das"
    """

    Observer.configure(pid, [{NoopPlugin, {"file:///", bmf}}, {Delay, 50}])
    TestListener.clear(listener)
    :ok = Observer.run(pid)

    Utils.wait_for fn -> length(TestListener.get(listener)) >= 2 end
    Observer.stop(pid, :stop)

    # take only the first two elements, since noop is extremely fast
    # and produces a huge amount of events.
    l = TestListener.get(listener) |> Enum.take(2)
    assert [{_, :start_observer, _}, {_, :exec_observer, _}] = l
  end

end


fix disassembler parsing issue

fix disassembler parsing issue

defmodule Neoscan.Vm.Disassembler do
  @moduledoc ""
  # credo:disable-for-lines:118
  @opcodes_list %{
    "00" => "PUSH0", # An empty array of bytes is pushed onto the stack.
    "PUSH0" => "PUSHF",
    "01" =>
      "PUSHBYTES1", # 0x01-0x4B The next opcode bytes is data to be pushed onto the stack
    "4b" => "PUSHBYTES75",
    "4c" =>
      "PUSHDATA1", # The next byte contains the number of bytes to be pushed onto the stack.
    "4d" =>
      "PUSHDATA2", # The next two bytes contain the number of bytes to be pushed onto the stack.
    "4e" =>
      "PUSHDATA4", # The next four bytes contain the number of bytes to be pushed onto the stack.
    "4f" => "PUSHM1", # The number -1 is pushed onto the stack.
    "51" => "PUSH1", # The number 1 is pushed onto the stack.
    "PUSH1" => "PUSHT",
    "52" => "PUSH2", # The number 2 is pushed onto the stack.
    "53" => "PUSH3", # The number 3 is pushed onto the stack.
    "54" => "PUSH4", # The number 4 is pushed onto the stack.
    "55" => "PUSH5", # The number 5 is pushed onto the stack.
    "56" => "PUSH6", # The number 6 is pushed onto the stack.
    "57" => "PUSH7", # The number 7 is pushed onto the stack.
    "58" => "PUSH8", # The number 8 is pushed onto the stack.
    "59" => "PUSH9", # The number 9 is pushed onto the stack.
    "5a" => "PUSH10", # The number 10 is pushed onto the stack.
    "5b" => "PUSH11", # The number 11 is pushed onto the stack.
    "5c" => "PUSH12", # The number 12 is pushed onto the stack.
    "5d" => "PUSH13", # The number 13 is pushed onto the stack.
    "5e" => "PUSH14", # The number 14 is pushed onto the stack.
    "5f" => "PUSH15", # The number 15 is pushed onto the stack.
    "60" => "PUSH16", # The number 16 is pushed onto the stack.
    "61" => "NOP", # Does nothing.
    "62" => "JMP",
    "63" => "JMPIF",
    "64" => "JMPIFNOT",
    "65" => "CALL",
    "66" => "RET",
    "67" => "APPCALL",
    "68" => "SYSCALL",
    "69" => "TAILCALL",
    "6a" => "DUPFROMALTSTACK",
    "6b" =>
      "TOALTSTACK", # Puts the input onto the top of the alt stack. Removes it from the main stack.
    "6c" =>
      "FROMALTSTACK", # Puts the input onto the top of the main stack. Removes it from the alt stack.
    "6d" => "XDROP",
    "72" => "XSWAP",
    "73" => "XTUCK",
    "74" => "DEPTH", # Puts the number of stack items onto the stack.
    "75" => "DROP", # Removes the top stack item.
    "76" => "DUP", # Duplicates the top stack item.
    "77" => "NIP", # Removes the second-to-top stack item.
    "78" => "OVER", # Copies the second-to-top stack item to the top.
    "79" => "PICK", # The item n back in the stack is copied to the top.
    "7a" => "ROLL", # The item n back in the stack is moved to the top.
    "7b" => "ROT", # The top three items on the stack are rotated to the left.
    "7c" => "SWAP", # The top two items on the stack are swapped.
    "7d" =>
      "TUCK", # The item at the top of the stack is copied and inserted before the second-to-top item.
    "7e" => "CAT", # Concatenates two strings.
    "7f" => "SUBSTR", # Returns a section of a string.
    "80" =>
      "LEFT", # Keeps only characters left of the specified point in a string.
    "81" =>
      "RIGHT", # Keeps only characters right of the specified point in a string.
    "82" => "SIZE", # Returns the length of the input string.
    "83" => "INVERT", # Flips all of the bits in the input.
    "84" => "AND", # Boolean and between each bit in the inputs.
    "85" => "OR", # Boolean or between each bit in the inputs.
    "86" => "XOR", # Boolean exclusive or between each bit in the inputs.
    "87" => "EQUAL", # Returns 1 if the inputs are exactly equal", 0 otherwise.
    "8b" => "INC", # 1 is added to the input.
    "8c" => "DEC", # 1 is subtracted from the input.
    "8d" => "SIGN",
    "8f" => "NEGATE", # The sign of the input is flipped.
    "90" => "ABS", # The input is made positive.
    "91" =>
      "NOT", # If the input is 0 or 1", it is flipped. Otherwise the output will be 0.
    "92" => "NZ", # Returns 0 if the input is 0. 1 otherwise.
    "93" => "ADD", # a is added to b.
    "94" => "SUB", # b is subtracted from a.
    "95" => "MUL", # a is multiplied by b.
    "96" => "DIV", # a is divided by b.
    "97" => "MOD", # Returns the remainder after dividing a by b.
    "98" => "SHL", # Shifts a left b bits, preserving sign.
    "99" => "SHR", # Shifts a right b bits, preserving sign.
    "9a" =>
      "BOOLAND", # If both a and b are not 0, the output is 1. Otherwise 0.
    "9b" => "BOOLOR", # If a or b is not 0, the output is 1. Otherwise 0.
    "9c" => "NUMEQUAL", # Returns 1 if the numbers are equal, 0 otherwise.
    "9e" =>
      "NUMNOTEQUAL", # Returns 1 if the numbers are not equal, 0 otherwise.
    "9f" => "LT", # Returns 1 if a is less than b, 0 otherwise.
    "a0" => "GT", # Returns 1 if a is greater than b, 0 otherwise.
    "a1" => "LTE", # Returns 1 if a is less than or equal to b, 0 otherwise.
    "a2" => "GTE", # Returns 1 if a is greater than or equal to b, 0 otherwise.
    "a3" => "MIN", # Returns the smaller of a and b.
    "a4" => "MAX", # Returns the larger of a and b.
    "a5" =>
      "WITHIN", # Returns 1 if x is within the specified range (left-inclusive), 0 otherwise.
    "a7" => "SHA1", # The input is hashed using SHA-1.
    "a8" => "SHA256", # The input is hashed using SHA-256.
    "a9" => "HASH160",
    "aa" => "HASH256",
    "ac" => "CHECKSIG",
    "ae" => "CHECKMULTISIG",
    "c0" => "ARRAYSIZE",
    "c1" => "PACK",
    "c2" => "UNPACK",
    "c3" => "PICKITEM",
    "c4" => "SETITEM",
    "c5" => "NEWARRAY", #用作引用類型
    "c6" => "NEWSTRUCT", #用作值類型
    "f0" => "THROW",
    "f1" => "THROWIFNOT"
  };

  @extended_opcodes %{
    "62" => 3,
    "63" => 3,
    "64" => 3,
    "65" => 3
  }

  def parse_script(hex_string) do
    hex_string
    |> String.codepoints
    |> Stream.chunk(2)
    |> Enum.map(&Enum.join/1)
    |> make_list()
    |> reduce_list_to_string()
    |> String.split("\n")
    |> parse_syscalls()
  end

  defp reduce_list_to_string(list) do
    Enum.reduce(
      list,
      "",
      fn (code, acc) ->
        if Map.has_key?(@opcodes_list, code) and String.length(code) == 2 do
          newline = if code == "68", do: "", else: "\n"
          acc <> @opcodes_list[code] <> newline
        else
          opcode_key = String.slice(code, 0..1)
          {opcode_keyword, push_bytes} =
            case Map.fetch(@opcodes_list, opcode_key) do
              {:ok, keyword} -> {keyword, ""}
              :error -> check_hex_num(opcode_key)
            end
          base_args = String.slice(code, 2..-1)
          args = cond do
            Map.has_key?(@extended_opcodes, opcode_key) ->
              get_jmp_num(base_args)
            push_bytes == "PUSHBYTES" ->
              "0x" <> base_args
            opcode_keyword == "APPCALL" or opcode_keyword == "TAILCALL" -> String.reverse(base_args)
            true ->
              base_args
          end
          acc <> push_bytes <> opcode_keyword <> ": " <> args <> "\n"
        end
      end
    )
  end

  defp get_jmp_num(args) do
    if String.ends_with?(args, "ff") do
      switch_args = "ffffff" <> String.slice(args, 0..1)
      {x, ""} = Integer.parse(switch_args, 16)
      <<signed_jmp_num :: integer - signed - 32>> = <<
        x :: integer - unsigned - 32
      >>
      to_string(signed_jmp_num)
    else
      switch_args = String.slice(args, 2..-1) <> String.slice(args, 0..1)
      {jmp_num, ""} = Integer.parse(switch_args, 16)
      to_string(jmp_num)
    end
  end

  defp check_hex_num(str) do
    case Integer.parse(str, 16) do
      {num, _} ->
        if num > 1 and num < 75 do
          {to_string(num), "PUSHBYTES"}
        else
          {"parsing error", ""}
        end
      :error ->
        {"parsing error", ""}
    end
  end

  defp make_list(initial_list) do
    initial_list
    |> Enum.reduce(
         {initial_list, []},
         fn (current, {remaining, acc}) ->
           remaining_head =
             case length(remaining) do
               0 -> nil
               _ -> hd(remaining)
             end
           with true <- remaining_head == current,
                {:ok, joins} <- extend_the_opcode(current) do
             {opcodes_to_be_joined, new_remaining} = Enum.split(
               remaining,
               joins
             )
             joined_item = Enum.join(opcodes_to_be_joined)
             {new_remaining, [joined_item | acc]}
           else
             :error -> {tl(remaining), [current | acc]}
             false -> {remaining, acc}
           end
         end
       )
    |> elem(1)
    |> Enum.reverse()
  end

  defp extend_the_opcode(current) do
    case Map.fetch(@extended_opcodes, current) do
      {:ok, extend_amount} -> {:ok, extend_amount}
      :error -> handle_opcode_error(current)
    end
  end

  defp handle_opcode_error(current) do
    case Integer.parse(current, 16) do
      {num, _} ->
        cond do
          num > 1 and num < 75 -> {:ok, num + 1}
          num === 103 or num === 105 -> {:ok, 20 + 1} # 20 bytes
          true -> :error
        end
      :error -> :error
    end
  end

  defp parse_syscalls(list) do
    final_opcodes =
      for line <- list do
        case String.contains?(line, "SYSCALL") do
          false -> line
          true ->
            newline = Regex.replace(~r/PUSHBYTES[0-9]{1,3} 0x/, line, " ")
            split_newline = String.split(newline, " ")
            syscall_fn = case Base.decode16(
                                Enum.at(split_newline, 1),
                                case: :lower
                              ) do
              {:ok, syscall_arg} -> syscall_arg
              :error -> "error parsing syscall fn arg"
            end
            Enum.at(split_newline, 0) <> " " <> syscall_fn
        end
      end
    if Enum.at(final_opcodes, length(final_opcodes) - 1) == "" do
      Enum.drop(final_opcodes, -1)
    else
      final_opcodes
    end
  end
end

defmodule Neoscan.Vm.Disassembler do
  @moduledoc ""
  # credo:disable-for-lines:118
  @opcodes_list %{
    "00" => "PUSH0", # An empty array of bytes is pushed onto the stack.
    "PUSH0" => "PUSHF",
    "01" =>
      "PUSHBYTES1", # 0x01-0x4B The next opcode bytes is data to be pushed onto the stack
    "4b" => "PUSHBYTES75",
    "4c" =>
      "PUSHDATA1", # The next byte contains the number of bytes to be pushed onto the stack.
    "4d" =>
      "PUSHDATA2", # The next two bytes contain the number of bytes to be pushed onto the stack.
    "4e" =>
      "PUSHDATA4", # The next four bytes contain the number of bytes to be pushed onto the stack.
    "4f" => "PUSHM1", # The number -1 is pushed onto the stack.
    "51" => "PUSH1", # The number 1 is pushed onto the stack.
    "PUSH1" => "PUSHT",
    "52" => "PUSH2", # The number 2 is pushed onto the stack.
    "53" => "PUSH3", # The number 3 is pushed onto the stack.
    "54" => "PUSH4", # The number 4 is pushed onto the stack.
    "55" => "PUSH5", # The number 5 is pushed onto the stack.
    "56" => "PUSH6", # The number 6 is pushed onto the stack.
    "57" => "PUSH7", # The number 7 is pushed onto the stack.
    "58" => "PUSH8", # The number 8 is pushed onto the stack.
    "59" => "PUSH9", # The number 9 is pushed onto the stack.
    "5a" => "PUSH10", # The number 10 is pushed onto the stack.
    "5b" => "PUSH11", # The number 11 is pushed onto the stack.
    "5c" => "PUSH12", # The number 12 is pushed onto the stack.
    "5d" => "PUSH13", # The number 13 is pushed onto the stack.
    "5e" => "PUSH14", # The number 14 is pushed onto the stack.
    "5f" => "PUSH15", # The number 15 is pushed onto the stack.
    "60" => "PUSH16", # The number 16 is pushed onto the stack.
    "61" => "NOP", # Does nothing.
    "62" => "JMP",
    "63" => "JMPIF",
    "64" => "JMPIFNOT",
    "65" => "CALL",
    "66" => "RET",
    "67" => "APPCALL",
    "68" => "SYSCALL",
    "69" => "TAILCALL",
    "6a" => "DUPFROMALTSTACK",
    "6b" =>
      "TOALTSTACK", # Puts the input onto the top of the alt stack. Removes it from the main stack.
    "6c" =>
      "FROMALTSTACK", # Puts the input onto the top of the main stack. Removes it from the alt stack.
    "6d" => "XDROP",
    "72" => "XSWAP",
    "73" => "XTUCK",
    "74" => "DEPTH", # Puts the number of stack items onto the stack.
    "75" => "DROP", # Removes the top stack item.
    "76" => "DUP", # Duplicates the top stack item.
    "77" => "NIP", # Removes the second-to-top stack item.
    "78" => "OVER", # Copies the second-to-top stack item to the top.
    "79" => "PICK", # The item n back in the stack is copied to the top.
    "7a" => "ROLL", # The item n back in the stack is moved to the top.
    "7b" => "ROT", # The top three items on the stack are rotated to the left.
    "7c" => "SWAP", # The top two items on the stack are swapped.
    "7d" =>
      "TUCK", # The item at the top of the stack is copied and inserted before the second-to-top item.
    "7e" => "CAT", # Concatenates two strings.
    "7f" => "SUBSTR", # Returns a section of a string.
    "80" =>
      "LEFT", # Keeps only characters left of the specified point in a string.
    "81" =>
      "RIGHT", # Keeps only characters right of the specified point in a string.
    "82" => "SIZE", # Returns the length of the input string.
    "83" => "INVERT", # Flips all of the bits in the input.
    "84" => "AND", # Boolean and between each bit in the inputs.
    "85" => "OR", # Boolean or between each bit in the inputs.
    "86" => "XOR", # Boolean exclusive or between each bit in the inputs.
    "87" => "EQUAL", # Returns 1 if the inputs are exactly equal", 0 otherwise.
    "8b" => "INC", # 1 is added to the input.
    "8c" => "DEC", # 1 is subtracted from the input.
    "8d" => "SIGN",
    "8f" => "NEGATE", # The sign of the input is flipped.
    "90" => "ABS", # The input is made positive.
    "91" =>
      "NOT", # If the input is 0 or 1", it is flipped. Otherwise the output will be 0.
    "92" => "NZ", # Returns 0 if the input is 0. 1 otherwise.
    "93" => "ADD", # a is added to b.
    "94" => "SUB", # b is subtracted from a.
    "95" => "MUL", # a is multiplied by b.
    "96" => "DIV", # a is divided by b.
    "97" => "MOD", # Returns the remainder after dividing a by b.
    "98" => "SHL", # Shifts a left b bits, preserving sign.
    "99" => "SHR", # Shifts a right b bits, preserving sign.
    "9a" =>
      "BOOLAND", # If both a and b are not 0, the output is 1. Otherwise 0.
    "9b" => "BOOLOR", # If a or b is not 0, the output is 1. Otherwise 0.
    "9c" => "NUMEQUAL", # Returns 1 if the numbers are equal, 0 otherwise.
    "9e" =>
      "NUMNOTEQUAL", # Returns 1 if the numbers are not equal, 0 otherwise.
    "9f" => "LT", # Returns 1 if a is less than b, 0 otherwise.
    "a0" => "GT", # Returns 1 if a is greater than b, 0 otherwise.
    "a1" => "LTE", # Returns 1 if a is less than or equal to b, 0 otherwise.
    "a2" => "GTE", # Returns 1 if a is greater than or equal to b, 0 otherwise.
    "a3" => "MIN", # Returns the smaller of a and b.
    "a4" => "MAX", # Returns the larger of a and b.
    "a5" =>
      "WITHIN", # Returns 1 if x is within the specified range (left-inclusive), 0 otherwise.
    "a7" => "SHA1", # The input is hashed using SHA-1.
    "a8" => "SHA256", # The input is hashed using SHA-256.
    "a9" => "HASH160",
    "aa" => "HASH256",
    "ac" => "CHECKSIG",
    "ae" => "CHECKMULTISIG",
    "c0" => "ARRAYSIZE",
    "c1" => "PACK",
    "c2" => "UNPACK",
    "c3" => "PICKITEM",
    "c4" => "SETITEM",
    "c5" => "NEWARRAY", #用作引用類型
    "c6" => "NEWSTRUCT", #用作值類型
    "f0" => "THROW",
    "f1" => "THROWIFNOT"
  };

  @extended_opcodes %{
    "62" => 3,
    "63" => 3,
    "64" => 3,
    "65" => 3
  }

  def parse_script(hex_string) do
    hex_string
    |> String.codepoints
    |> Stream.chunk(2)
    |> Enum.map(&Enum.join/1)
    |> make_list()
    |> reduce_list_to_string()
    |> String.split("\n")
    |> parse_syscalls()
  end

  defp reduce_list_to_string(list) do
    Enum.reduce(
      list,
      "",
      fn (code, acc) ->
        if Map.has_key?(@opcodes_list, code) and String.length(code) == 2 do
          newline = if code == "68", do: "", else: "\n"
          acc <> @opcodes_list[code] <> newline
        else
          opcode_key = String.slice(code, 0..1)
          {opcode_keyword, push_bytes} =
            case Map.fetch(@opcodes_list, opcode_key) do
              {:ok, keyword} -> {keyword, ""}
              :error -> check_hex_num(opcode_key)
            end
          base_args = String.slice(code, 2..-1)
          args = cond do
            Map.has_key?(@extended_opcodes, opcode_key) ->
              get_jmp_num(base_args)
            push_bytes == "PUSHBYTES" ->
              "0x" <> base_args
            true ->
              base_args
          end
          acc <> push_bytes <> opcode_keyword <> ": " <> args <> "\n"
        end
      end
    )
  end

  defp get_jmp_num(args) do
    if String.ends_with?(args, "ff") do
      switch_args = "ffffff" <> String.slice(args, 0..1)
      {x, ""} = Integer.parse(switch_args, 16)
      <<signed_jmp_num :: integer - signed - 32>> = <<
        x :: integer - unsigned - 32
      >>
      to_string(signed_jmp_num)
    else
      switch_args = String.slice(args, 2..-1) <> String.slice(args, 0..1)
      {jmp_num, ""} = Integer.parse(switch_args, 16)
      to_string(jmp_num)
    end
  end

  defp check_hex_num(str) do
    case Integer.parse(str, 16) do
      {num, _} ->
        if num > 1 and num < 75 do
          {to_string(num), "PUSHBYTES"}
        else
          {"parsing error", ""}
        end
      :error ->
        {"parsing error", ""}
    end
  end

  defp make_list(initial_list) do
    initial_list
    |> Enum.reduce(
         {initial_list, []},
         fn (current, {remaining, acc}) ->
           remaining_head =
             case length(remaining) do
               0 -> nil
               _ -> hd(remaining)
             end
           with true <- remaining_head == current,
                {:ok, joins} <- extend_the_opcode(current) do
             {opcodes_to_be_joined, new_remaining} = Enum.split(
               remaining,
               joins
             )
             joined_item = Enum.join(opcodes_to_be_joined)
             {new_remaining, [joined_item | acc]}
           else
             :error -> {tl(remaining), [current | acc]}
             false -> {remaining, acc}
           end
         end
       )
    |> elem(1)
    |> Enum.reverse()
  end

  defp extend_the_opcode(current) do
    case Map.fetch(@extended_opcodes, current) do
      {:ok, extend_amount} -> {:ok, extend_amount}
      :error -> handle_opcode_error(current)
    end
  end

  defp handle_opcode_error(current) do
    case Integer.parse(current, 16) do
      {num, _} -> if num > 1 and num < 75, do: {:ok, num + 1}, else: :error
      :error -> :error
    end
  end

  defp parse_syscalls(list) do
    final_opcodes =
      for line <- list do
        case String.contains?(line, "SYSCALL") do
          false -> line
          true ->
            newline = Regex.replace(~r/PUSHBYTES[0-9]{1,3} 0x/, line, " ")
            split_newline = String.split(newline, " ")
            syscall_fn = case Base.decode16(
                                Enum.at(split_newline, 1),
                                case: :lower
                              ) do
              {:ok, syscall_arg} -> syscall_arg
              :error -> "error parsing syscall fn arg"
            end
            Enum.at(split_newline, 0) <> " " <> syscall_fn
        end
      end
    if Enum.at(final_opcodes, length(final_opcodes) - 1) == "" do
      Enum.drop(final_opcodes, -1)
    else
      final_opcodes
    end
  end
end


Do not override builtin type

Do not override builtin type

defmodule Mix.ProjectStack do
  # Keeps the project stack.
  @moduledoc false

  use GenServer.Behaviour
  @timeout 30_000

  @typep file    :: binary
  @typep config  :: Keyword.t
  @typep project :: { module, config, file }

  defrecord State, stack: [], post_config: [], cache: HashDict.new
  defrecord Project, name: nil, config: nil, file: nil,
                     recursing?: false, io_done: false, tasks: HashSet.new

  @spec start_link :: { :ok, pid }
  def start_link() do
    :gen_server.start_link({ :local, __MODULE__ }, __MODULE__, [], [])
  end

  @spec push(module, config, file) :: :ok | { :error, file }
  def push(module, config, file) do
    call { :push, module, config, file }
  end

  @spec pop() :: project | nil
  def pop do
    call :pop
  end

  @spec peek() :: project | nil
  def peek do
    call :peek
  end

  @spec post_config(config) :: :ok
  def post_config(config) do
    cast { :post_config, config }
  end

  @spec output_app?() :: boolean
  def output_app? do
    call :output_app?
  end

  @doc """
  Enables the recursion for the project at the top of the stack.
  Returns true if recursion was enabled or false if the project
  already had recursion enabled or there is no project in the stack.
  """
  @spec enable_recursion :: boolean
  def enable_recursion do
    call :enable_recursion
  end

  @doc """
  Disables the recursion for the project in the stack.
  Returns true if recursion was disable or false if there
  is no project or recursion was not enabled.
  """
  @spec disable_recursion :: boolean
  def disable_recursion do
    call :disable_recursion
  end

  @spec read_cache(term) :: term
  def read_cache(key) do
    call({ :read_cache, key })
  end

  @spec write_cache(term, term) :: :ok
  def write_cache(key, value) do
    cast({ :write_cache, key, value })
  end

  @spec clear_cache :: :ok
  def clear_cache do
    cast(:clear_cache)
  end

  defp call(arg) do
    :gen_server.call(__MODULE__, arg, @timeout)
  end

  defp cast(arg) do
    :gen_server.cast(__MODULE__, arg)
  end

  ## Callbacks

  def init([]) do
    { :ok, State[] }
  end

  def handle_call({ :push, name, config, file }, _from, State[stack: stack] = state) do
    config  = Keyword.merge(config, state.post_config)
    project = Project[name: name, config: config, file: file]

    cond do
      file = find_project_named(name, stack) ->
        { :reply, { :error, file }, state }
      true ->
        { :reply, :ok, state.post_config([]).update_stack(&[project|&1]) }
    end
  end

  def handle_call(:pop, _from, State[stack: stack] = state) do
    case stack do
      [h|t] -> { :reply, project_to_tuple(h), state.stack(t) }
      [] -> { :reply, nil, state }
    end
  end

  def handle_call(:peek, _from, State[stack: stack] = state) do
    case stack do
      [h|_] -> { :reply, project_to_tuple(h), state }
      [] -> { :reply, nil, state }
    end
  end

  def handle_call(:output_app?, _from, State[stack: stack] = state) do
    case stack do
      [Project[]=h|t] ->
        output = not h.io_done and not umbrella?(stack) and in_umbrella?(stack)
        { :reply, output, state.stack([h.io_done(true)|t]) }
      [] ->
        { :reply, false, state }
    end
  end

  def handle_call(:enable_recursion, _from, State[stack: stack] = state) do
    case stack do
      [Project[]=h|t] ->
        { :reply, not h.recursing?, state.stack([h.recursing?(true)|t]) }
      _ ->
        { :reply, false, state }
    end
  end

  def handle_call(:disable_recursion, _from, State[stack: stack] = state) do
    case stack do
      [Project[]=h|t] ->
        { :reply, h.recursing?, state.stack([h.recursing?(false)|t]) }
      _ ->
        { :reply, false, state }
    end
  end

  def handle_call({ :read_cache, key }, _from, State[cache: cache] = state) do
    { :reply, cache[key], state }
  end

  def handle_call(request, from, config) do
    super(request, from, config)
  end

  def handle_cast({ :post_config, value }, State[] = state) do
    { :noreply, state.update_post_config(&Keyword.merge(&1, value)) }
  end

  def handle_cast({ :write_cache, key, value }, State[] = state) do
    { :noreply, state.update_cache(&Dict.put(&1, key, value)) }
  end

  def handle_cast(:clear_cache, State[] = state) do
    { :noreply, state.update_cache(&Dict.empty/1) }
  end

  def handle_cast(request, state) do
    super(request, state)
  end

  defp in_umbrella?(stack) do
    Enum.any?(stack, fn(Project[config: conf]) ->
      conf[:apps_path] != nil
    end)
  end

  defp umbrella?(stack) do
    case stack do
      [Project[name: name, config: config]|_] when name != nil -> config[:apps_path] != nil
      _ -> false
    end
  end

  defp find_project_named(name, stack) do
    name && Enum.find_value(stack, fn
      Project[name: ^name, file: file] -> file
      Project[] -> nil
    end)
  end

  defp project_to_tuple(Project[name: name, config: config, file: file]) do
    { name, config, file }
  end
end

defmodule Mix.ProjectStack do
  # Keeps the project stack.
  @moduledoc false

  use GenServer.Behaviour
  @timeout 30_000

  @typep module  :: atom | nil
  @typep file    :: binary
  @typep config  :: Keyword.t
  @typep project :: { module, config, file }

  defrecord State, stack: [], post_config: [], cache: HashDict.new
  defrecord Project, name: nil, config: nil, file: nil,
                     recursing?: false, io_done: false, tasks: HashSet.new

  @spec start_link :: { :ok, pid }
  def start_link() do
    :gen_server.start_link({ :local, __MODULE__ }, __MODULE__, [], [])
  end

  @spec push(module, config, file) :: :ok | { :error, file }
  def push(module, config, file) do
    call { :push, module, config, file }
  end

  @spec pop() :: project | nil
  def pop do
    call :pop
  end

  @spec peek() :: project | nil
  def peek do
    call :peek
  end

  @spec post_config(config) :: :ok
  def post_config(config) do
    cast { :post_config, config }
  end

  @spec output_app?() :: boolean
  def output_app? do
    call :output_app?
  end

  @doc """
  Enables the recursion for the project at the top of the stack.
  Returns true if recursion was enabled or false if the project
  already had recursion enabled or there is no project in the stack.
  """
  @spec enable_recursion :: boolean
  def enable_recursion do
    call :enable_recursion
  end

  @doc """
  Disables the recursion for the project in the stack.
  Returns true if recursion was disable or false if there
  is no project or recursion was not enabled.
  """
  @spec disable_recursion :: boolean
  def disable_recursion do
    call :disable_recursion
  end

  @spec read_cache(term) :: term
  def read_cache(key) do
    call({ :read_cache, key })
  end

  @spec write_cache(term, term) :: :ok
  def write_cache(key, value) do
    cast({ :write_cache, key, value })
  end

  @spec clear_cache :: :ok
  def clear_cache do
    cast(:clear_cache)
  end

  defp call(arg) do
    :gen_server.call(__MODULE__, arg, @timeout)
  end

  defp cast(arg) do
    :gen_server.cast(__MODULE__, arg)
  end

  ## Callbacks

  def init([]) do
    { :ok, State[] }
  end

  def handle_call({ :push, name, config, file }, _from, State[stack: stack] = state) do
    config  = Keyword.merge(config, state.post_config)
    project = Project[name: name, config: config, file: file]

    cond do
      file = find_project_named(name, stack) ->
        { :reply, { :error, file }, state }
      true ->
        { :reply, :ok, state.post_config([]).update_stack(&[project|&1]) }
    end
  end

  def handle_call(:pop, _from, State[stack: stack] = state) do
    case stack do
      [h|t] -> { :reply, project_to_tuple(h), state.stack(t) }
      [] -> { :reply, nil, state }
    end
  end

  def handle_call(:peek, _from, State[stack: stack] = state) do
    case stack do
      [h|_] -> { :reply, project_to_tuple(h), state }
      [] -> { :reply, nil, state }
    end
  end

  def handle_call(:output_app?, _from, State[stack: stack] = state) do
    case stack do
      [Project[]=h|t] ->
        output = not h.io_done and not umbrella?(stack) and in_umbrella?(stack)
        { :reply, output, state.stack([h.io_done(true)|t]) }
      [] ->
        { :reply, false, state }
    end
  end

  def handle_call(:enable_recursion, _from, State[stack: stack] = state) do
    case stack do
      [Project[]=h|t] ->
        { :reply, not h.recursing?, state.stack([h.recursing?(true)|t]) }
      _ ->
        { :reply, false, state }
    end
  end

  def handle_call(:disable_recursion, _from, State[stack: stack] = state) do
    case stack do
      [Project[]=h|t] ->
        { :reply, h.recursing?, state.stack([h.recursing?(false)|t]) }
      _ ->
        { :reply, false, state }
    end
  end

  def handle_call({ :read_cache, key }, _from, State[cache: cache] = state) do
    { :reply, cache[key], state }
  end

  def handle_call(request, from, config) do
    super(request, from, config)
  end

  def handle_cast({ :post_config, value }, State[] = state) do
    { :noreply, state.update_post_config(&Keyword.merge(&1, value)) }
  end

  def handle_cast({ :write_cache, key, value }, State[] = state) do
    { :noreply, state.update_cache(&Dict.put(&1, key, value)) }
  end

  def handle_cast(:clear_cache, State[] = state) do
    { :noreply, state.update_cache(&Dict.empty/1) }
  end

  def handle_cast(request, state) do
    super(request, state)
  end

  defp in_umbrella?(stack) do
    Enum.any?(stack, fn(Project[config: conf]) ->
      conf[:apps_path] != nil
    end)
  end

  defp umbrella?(stack) do
    case stack do
      [Project[name: name, config: config]|_] when name != nil -> config[:apps_path] != nil
      _ -> false
    end
  end

  defp find_project_named(name, stack) do
    name && Enum.find_value(stack, fn
      Project[name: ^name, file: file] -> file
      Project[] -> nil
    end)
  end

  defp project_to_tuple(Project[name: name, config: config, file: file]) do
    { name, config, file }
  end
end


fix idle_timeout

fix idle_timeout

Fixes #108

defmodule GRPC.Adapter.Cowboy do
  @moduledoc false

  # A server(`GRPC.Server`) adapter using Cowboy.
  # Cowboy req will be stored in `:payload` of `GRPC.Server.Stream`.

  require Logger
  alias GRPC.Adapter.Cowboy.Handler, as: Handler

  @default_num_acceptors 20

  # Only used in starting a server manually using `GRPC.Server.start(servers)`
  @spec start(atom, GRPC.Server.servers_map(), non_neg_integer, keyword) ::
          {:ok, pid, non_neg_integer}
  def start(endpoint, servers, port, opts) do
    start_args = cowboy_start_args(endpoint, servers, port, opts)
    start_func = if opts[:cred], do: :start_tls, else: :start_clear

    {:ok, pid} = apply(:cowboy, start_func, start_args)

    port = :ranch.get_port(servers_name(endpoint, servers))
    {:ok, pid, port}
  end

  @spec child_spec(atom, GRPC.Server.servers_map(), non_neg_integer, Keyword.t()) ::
          Supervisor.Spec.spec()
  def child_spec(endpoint, servers, port, opts) do
    [ref, trans_opts, proto_opts] = cowboy_start_args(endpoint, servers, port, opts)
    trans_opts = Map.put(trans_opts, :connection_type, :supervisor)

    {transport, protocol} =
      if opts[:cred] do
        {:ranch_ssl, :cowboy_tls}
      else
        {:ranch_tcp, :cowboy_clear}
      end

    {ref, mfa, type, timeout, kind, modules} =
      :ranch.child_spec(ref, transport, trans_opts, protocol, proto_opts)

    scheme = if opts[:cred], do: :https, else: :http
    # Wrap real mfa to print starting log
    mfa = {__MODULE__, :start_link, [scheme, endpoint, servers, mfa]}
    {ref, mfa, type, timeout, kind, modules}
  end

  # spec: :supervisor.mfargs doesn't work
  @spec start_link(atom, atom, GRPC.Server.servers_map(), any) :: {:ok, pid} | {:error, any}
  def start_link(scheme, endpoint, servers, {m, f, [ref | _] = a}) do
    case apply(m, f, a) do
      {:ok, pid} ->
        Logger.info(running_info(scheme, endpoint, servers, ref))
        {:ok, pid}

      {:error, {:shutdown, {_, _, {{_, {:error, :eaddrinuse}}, _}}}} = error ->
        Logger.error([
          running_info(scheme, endpoint, servers, ref),
          " failed, port already in use"
        ])

        error

      {:error, _} = error ->
        error
    end
  end

  @spec stop(atom, GRPC.Server.servers_map()) :: :ok | {:error, :not_found}
  def stop(endpoint, servers) do
    :cowboy.stop_listener(servers_name(endpoint, servers))
  end

  @spec read_body(GRPC.Adapter.Cowboy.Handler.state()) :: {:ok, binary}
  def read_body(%{pid: pid}) do
    Handler.read_full_body(pid)
  end

  @spec reading_stream(GRPC.Adapter.Cowboy.Handler.state()) :: Enumerable.t()
  def reading_stream(%{pid: pid}) do
    Stream.unfold(%{pid: pid, need_more: true, buffer: <<>>}, fn acc -> read_stream(acc) end)
  end

  defp read_stream(%{buffer: <<>>, finished: true}), do: nil

  defp read_stream(%{pid: pid, buffer: buffer, need_more: true} = s) do
    case Handler.read_body(pid) do
      {:ok, data} ->
        new_data = buffer <> data
        new_s = %{pid: pid, finished: true, need_more: false, buffer: new_data}
        read_stream(new_s)

      {:more, data} ->
        data = buffer <> data
        new_s = s |> Map.put(:need_more, false) |> Map.put(:buffer, data)
        read_stream(new_s)
    end
  end

  defp read_stream(%{buffer: buffer} = s) do
    case GRPC.Message.get_message(buffer) do
      {message, rest} ->
        new_s = s |> Map.put(:buffer, rest)
        {message, new_s}

      _ ->
        read_stream(Map.put(s, :need_more, true))
    end
  end

  @spec send_reply(GRPC.Adapter.Cowboy.Handler.state(), binary, keyword) :: any
  def send_reply(%{pid: pid}, data, opts) do
    Handler.stream_body(pid, data, opts, :nofin)
  end

  def send_headers(%{pid: pid}, headers) do
    Handler.stream_reply(pid, 200, headers)
  end

  def set_headers(%{pid: pid}, headers) do
    Handler.set_resp_headers(pid, headers)
  end

  def set_resp_trailers(%{pid: pid}, trailers) do
    Handler.set_resp_trailers(pid, trailers)
  end

  def send_trailers(%{pid: pid}, trailers) do
    Handler.stream_trailers(pid, trailers)
  end

  def get_headers(%{pid: pid}) do
    Handler.get_headers(pid)
  end

  def set_compressor(%{pid: pid}, compressor) do
    Handler.set_compressor(pid, compressor)
  end

  defp cowboy_start_args(endpoint, servers, port, opts) do
    dispatch =
      :cowboy_router.compile([
        {:_, [{:_, GRPC.Adapter.Cowboy.Handler, {endpoint, servers, Enum.into(opts, %{})}}]}
      ])

    idle_timeout = Keyword.get(opts, :idle_timeout, :infinity)

    # https://ninenines.eu/docs/en/cowboy/2.7/manual/cowboy_http2/
    opts =
      Map.merge(
        %{
          env: %{dispatch: dispatch},
          idle_timeout: idle_timeout,
          inactivity_timeout: idle_timeout,
          settings_timeout: idle_timeout,
          stream_handlers: [:grpc_stream_h],
          # The default option is small
          # https://github.com/ninenines/cowboy/issues/1398
          # If there are 1000 streams in one connection, then 1000/s frames per stream.
          max_received_frame_rate: {10_000_000, 10_000},
          max_reset_stream_rate: {10_000, 10_000}
        },
        Enum.into(opts, %{})
      )

    [
      servers_name(endpoint, servers),
      %{num_acceptors: @default_num_acceptors, socket_opts: socket_opts(port, opts)},
      opts
    ]
  end

  defp socket_opts(port, opts) do
    socket_opts = [port: port]
    socket_opts = if opts[:ip], do: [{:ip, opts[:ip]} | socket_opts], else: socket_opts

    if opts[:cred] do
      opts[:cred].ssl ++
        [
          # These NPN/ALPN options are hardcoded in :cowboy.start_tls/3 (when calling start/3),
          # but not in :ranch.child_spec/5 (when calling child_spec/3). We must make sure they
          # are always provided.
          {:next_protocols_advertised, ["h2", "http/1.1"]},
          {:alpn_preferred_protocols, ["h2", "http/1.1"]}
          | socket_opts
        ]
    else
      socket_opts
    end
  end

  defp running_info(scheme, endpoint, servers, ref) do
    {addr, port} = :ranch.get_addr(ref)

    addr_str =
      case addr do
        :local ->
          port

        addr ->
          "#{:inet.ntoa(addr)}:#{port}"
      end

    "Running #{servers_name(endpoint, servers)} with Cowboy using #{scheme}://#{addr_str}"
  end

  defp servers_name(nil, servers) do
    servers |> Map.values() |> Enum.map(fn s -> inspect(s) end) |> Enum.join(",")
  end

  defp servers_name(endpoint, _) do
    inspect(endpoint)
  end
end

defmodule GRPC.Adapter.Cowboy do
  @moduledoc false

  # A server(`GRPC.Server`) adapter using Cowboy.
  # Cowboy req will be stored in `:payload` of `GRPC.Server.Stream`.

  require Logger
  alias GRPC.Adapter.Cowboy.Handler, as: Handler

  @default_num_acceptors 20

  # Only used in starting a server manually using `GRPC.Server.start(servers)`
  @spec start(atom, GRPC.Server.servers_map(), non_neg_integer, keyword) ::
          {:ok, pid, non_neg_integer}
  def start(endpoint, servers, port, opts) do
    start_args = cowboy_start_args(endpoint, servers, port, opts)
    start_func = if opts[:cred], do: :start_tls, else: :start_clear

    {:ok, pid} = apply(:cowboy, start_func, start_args)

    port = :ranch.get_port(servers_name(endpoint, servers))
    {:ok, pid, port}
  end

  @spec child_spec(atom, GRPC.Server.servers_map(), non_neg_integer, Keyword.t()) ::
          Supervisor.Spec.spec()
  def child_spec(endpoint, servers, port, opts) do
    [ref, trans_opts, proto_opts] = cowboy_start_args(endpoint, servers, port, opts)
    trans_opts = Map.put(trans_opts, :connection_type, :supervisor)

    {transport, protocol} =
      if opts[:cred] do
        {:ranch_ssl, :cowboy_tls}
      else
        {:ranch_tcp, :cowboy_clear}
      end

    {ref, mfa, type, timeout, kind, modules} =
      :ranch.child_spec(ref, transport, trans_opts, protocol, proto_opts)

    scheme = if opts[:cred], do: :https, else: :http
    # Wrap real mfa to print starting log
    mfa = {__MODULE__, :start_link, [scheme, endpoint, servers, mfa]}
    {ref, mfa, type, timeout, kind, modules}
  end

  # spec: :supervisor.mfargs doesn't work
  @spec start_link(atom, atom, GRPC.Server.servers_map(), any) :: {:ok, pid} | {:error, any}
  def start_link(scheme, endpoint, servers, {m, f, [ref | _] = a}) do
    case apply(m, f, a) do
      {:ok, pid} ->
        Logger.info(running_info(scheme, endpoint, servers, ref))
        {:ok, pid}

      {:error, {:shutdown, {_, _, {{_, {:error, :eaddrinuse}}, _}}}} = error ->
        Logger.error([
          running_info(scheme, endpoint, servers, ref),
          " failed, port already in use"
        ])

        error

      {:error, _} = error ->
        error
    end
  end

  @spec stop(atom, GRPC.Server.servers_map()) :: :ok | {:error, :not_found}
  def stop(endpoint, servers) do
    :cowboy.stop_listener(servers_name(endpoint, servers))
  end

  @spec read_body(GRPC.Adapter.Cowboy.Handler.state()) :: {:ok, binary}
  def read_body(%{pid: pid}) do
    Handler.read_full_body(pid)
  end

  @spec reading_stream(GRPC.Adapter.Cowboy.Handler.state()) :: Enumerable.t()
  def reading_stream(%{pid: pid}) do
    Stream.unfold(%{pid: pid, need_more: true, buffer: <<>>}, fn acc -> read_stream(acc) end)
  end

  defp read_stream(%{buffer: <<>>, finished: true}), do: nil

  defp read_stream(%{pid: pid, buffer: buffer, need_more: true} = s) do
    case Handler.read_body(pid) do
      {:ok, data} ->
        new_data = buffer <> data
        new_s = %{pid: pid, finished: true, need_more: false, buffer: new_data}
        read_stream(new_s)

      {:more, data} ->
        data = buffer <> data
        new_s = s |> Map.put(:need_more, false) |> Map.put(:buffer, data)
        read_stream(new_s)
    end
  end

  defp read_stream(%{buffer: buffer} = s) do
    case GRPC.Message.get_message(buffer) do
      {message, rest} ->
        new_s = s |> Map.put(:buffer, rest)
        {message, new_s}

      _ ->
        read_stream(Map.put(s, :need_more, true))
    end
  end

  @spec send_reply(GRPC.Adapter.Cowboy.Handler.state(), binary, keyword) :: any
  def send_reply(%{pid: pid}, data, opts) do
    Handler.stream_body(pid, data, opts, :nofin)
  end

  def send_headers(%{pid: pid}, headers) do
    Handler.stream_reply(pid, 200, headers)
  end

  def set_headers(%{pid: pid}, headers) do
    Handler.set_resp_headers(pid, headers)
  end

  def set_resp_trailers(%{pid: pid}, trailers) do
    Handler.set_resp_trailers(pid, trailers)
  end

  def send_trailers(%{pid: pid}, trailers) do
    Handler.stream_trailers(pid, trailers)
  end

  def get_headers(%{pid: pid}) do
    Handler.get_headers(pid)
  end

  def set_compressor(%{pid: pid}, compressor) do
    Handler.set_compressor(pid, compressor)
  end

  defp cowboy_start_args(endpoint, servers, port, opts) do
    dispatch =
      :cowboy_router.compile([
        {:_, [{:_, GRPC.Adapter.Cowboy.Handler, {endpoint, servers, Enum.into(opts, %{})}}]}
      ])

    idle_timeout = Keyword.get(opts, :idle_timeout, :infinity)
    # The default option is small
    # https://github.com/ninenines/cowboy/issues/1398
    max_received_frame_rate = Keyword.get(opts, :max_received_frame_rate, {1000_000, 10_000})
    max_reset_stream_rate = Keyword.get(opts, :max_reset_stream_rate, {1000, 10_000})

    [
      servers_name(endpoint, servers),
      %{
        num_acceptors: @default_num_acceptors,
        socket_opts: socket_opts(port, opts)
      },
      # https://ninenines.eu/docs/en/cowboy/2.7/manual/cowboy_http2/
      %{
        env: %{dispatch: dispatch},
        inactivity_timeout: idle_timeout,
        settings_timeout: idle_timeout,
        stream_handlers: [:grpc_stream_h],
        max_received_frame_rate: max_received_frame_rate,
        max_reset_stream_rate: max_reset_stream_rate
      }
    ]
  end

  defp socket_opts(port, opts) do
    socket_opts = [port: port]
    socket_opts = if opts[:ip], do: [{:ip, opts[:ip]} | socket_opts], else: socket_opts

    if opts[:cred] do
      opts[:cred].ssl ++
        [
          # These NPN/ALPN options are hardcoded in :cowboy.start_tls/3 (when calling start/3),
          # but not in :ranch.child_spec/5 (when calling child_spec/3). We must make sure they
          # are always provided.
          {:next_protocols_advertised, ["h2", "http/1.1"]},
          {:alpn_preferred_protocols, ["h2", "http/1.1"]}
          | socket_opts
        ]
    else
      socket_opts
    end
  end

  defp running_info(scheme, endpoint, servers, ref) do
    {addr, port} = :ranch.get_addr(ref)

    addr_str =
      case addr do
        :local ->
          port

        addr ->
          "#{:inet.ntoa(addr)}:#{port}"
      end

    "Running #{servers_name(endpoint, servers)} with Cowboy using #{scheme}://#{addr_str}"
  end

  defp servers_name(nil, servers) do
    servers |> Map.values() |> Enum.map(fn s -> inspect(s) end) |> Enum.join(",")
  end

  defp servers_name(endpoint, _) do
    inspect(endpoint)
  end
end