diff --git a/lib/bruce_hedwig/score_responder.ex b/lib/bruce_hedwig/score_responder.ex index 65bea7f..2d99008 100644 --- a/lib/bruce_hedwig/score_responder.ex +++ b/lib/bruce_hedwig/score_responder.ex @@ -6,25 +6,25 @@ defmodule BruceHedwig.ScoreResponder do @usage """ - hedwig help - Gives a thing a positive point + subject++ - Gives a thing a positive point """ hear ~r/(?\A.+)\s*\+\+\s*for\s+(?.+\Z)/i, msg do - ScoreServer.inc(msg.matches["subject"], msg.matches["reason"]) - reply msg, "Noted!" + current = ScoreServer.inc(msg.matches["subject"], msg.matches["reason"]) + reply msg, "Noted! #{msg.matches["subject"]} has #{current} points" end @usage """ - hedwig help - Gives a thing a negative point + subject-- - Gives a thing a negative point """ hear ~r/(?\A.+)\s*\-\-\s*for\s+(?.+\Z)/i, msg do - ScoreServer.dec(msg.matches["subject"], msg.matches["reason"]) - reply msg, "Noted!" + current = ScoreServer.dec(msg.matches["subject"], msg.matches["reason"]) + reply msg, "Noted! #{msg.matches["subject"]} has #{current} points" end @usage """ - hedwig help - Lists the current scores in a semi formatted way + list scores - Lists the current scores in a semi formatted way """ - hear ~r/(what is the score?|tell me the score|list scores)/i, msg do - reply msg, ScoreServer.scores + respond ~r/(list scores)/i, msg do + reply msg, ScoreServer.html_scores end end diff --git a/lib/bruce_hedwig/score_server.ex b/lib/bruce_hedwig/score_server.ex index 2b0fe25..6161672 100644 --- a/lib/bruce_hedwig/score_server.ex +++ b/lib/bruce_hedwig/score_server.ex @@ -1,86 +1,139 @@ defmodule BruceHedwig.ScoreServer do + require Logger + use GenServer - require Logger - # TODO: This registration will fail *in test* when this application is - # added to the supervisor tree. Look into OTP testing strategies - def start_link do - GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + def lookup(name) do + GenServer.call(:score_server, {:lookup, String.capitalize(name)}) end def inc(name, reason \\ "") do - GenServer.call(__MODULE__, {:inc, String.capitalize(name), reason}) + GenServer.call(:score_server, {:inc, String.capitalize(name), reason}) end def dec(name, reason \\ "") do - GenServer.call(__MODULE__, {:dec, String.capitalize(name), reason}) + GenServer.call(:score_server, {:inc, String.capitalize(name), reason}) end - def score(name) do - GenServer.call(__MODULE__, {:score, String.capitalize(name)}) + def scores do + GenServer.call(:score_server, {:scores}) end - def raw_scores do - GenServer.call(__MODULE__, {:scores}) + def html_scores do + GenServer.call(:score_server, {:html_scores}) end - def scores do - scores = GenServer.call(__MODULE__, {:scores}) - "\n* " <> Enum.join(Enum.map(Map.keys(scores), fn k -> - format_score(k, scores[k]) - end), "\n* ") - end - - defp format_score(name, scores) do - total = total_score(scores) - score_by_reason = Enum.group_by(scores, fn {_s,r} -> r end) - sub_scores = Enum.map(Map.keys(score_by_reason), fn reason -> - if reason != "" do - scores = score_by_reason[reason] - "#{total_score(scores)} points for #{reason}" - end - end) - - if sub_scores != [nil] do - "#{name} has #{total} points for the following reasons:#{Enum.join(sub_scores, "\n\t")}" - else - "#{name} has #{total} points" + def total_score(scores) do + Enum.reduce(scores, 0, fn ({s, _r}, acc) -> s + acc end) + end + + def keys(score_table) do + Enum.map(get_ets_keys_lazy(score_table), &(&1)) + end + + def get_ets_keys_lazy(table_name) when is_atom(table_name) do + eot = :"$end_of_table" + + Stream.resource( + fn -> [] end, + + fn acc -> + case acc do + [] -> + case :dets.first(table_name) do + ^eot -> {:halt, acc} + first_key -> {[first_key], first_key} + end + + acc -> + case :dets.next(table_name, acc) do + ^eot -> {:halt, acc} + next_key -> {[next_key], next_key} + end + end + end, + + fn _acc -> :ok end + ) + end + + # TODO: This registration will fail *in test* when this application is + # added to the supervisor tree. Look into OTP testing strategies + def start_link do + GenServer.start_link(__MODULE__, nil, name: :score_server) + end + + def init(_) do + {:ok, score_table} = :dets.open_file(:score_table, [type: :set]) + + {:ok, score_table} + end + + def handle_call({:lookup, name}, _from, score_table) do + value = case :dets.lookup(score_table, name) do + [{^name, score_list}] -> score_list + _ -> [{0, "existing"}] end + + {:reply, value, score_table} end - defp total_score(scores) do - Enum.reduce(scores, 0, fn ({s, _r}, acc) -> s + acc end) + def handle_call({:inc, name, reason}, _from, score_table) do + scores = case :dets.lookup(score_table, name) do + [{^name, score_list}] -> score_list + _ -> [{0, "existing"}] + end + + new_score = elem(Enum.at(scores, -1), 0) + 1 + new_scores = [ {new_score, reason} | scores ] + + :dets.insert(score_table, {name, new_scores}) + + {:reply, total_score(new_scores), score_table} end - ## Server Callbacks + def handle_call({:dec, name, reason}, _from, score_table) do + scores = case :dets.lookup(score_table, name) do + [{^name, score_list}] -> score_list + _ -> [{0, "existing"}] + end + + new_score = elem(Enum.at(scores, -1), 0) - 1 + new_scores = [ {new_score, reason} | scores ] - def init(:ok) do - {:ok, %{}} + :dets.insert(score_table, {name, new_scores}) + + {:reply, total_score(new_scores), score_table} end - def handle_call({:inc, name, reason}, _from, names) do - state = Map.get(names, name, [{0, ""}]) - new_score = elem(Enum.at(state, -1), 0) + 1 - new_scores = [ {new_score, reason} | state ] - - {:reply, total_score(new_scores), Map.put(names, name, new_scores)} + def handle_call({:scores}, _from, score_table) do + s = for key <- keys(score_table), into: %{}, do: {key, lookup(key)} + {:reply, s, score_table} end - def handle_call({:dec, name, reason}, _from, names) do - state = Map.get(names, name, [{0, ""}]) - new_score = elem(Enum.at(state, -1), 0) - 1 - new_scores = [ {new_score, reason} | state ] + def handle_call({:html_scores}, _from, score_table) do + formatted = for key <- keys(score_table), into: "" do + scores = case :dets.lookup(score_table, key) do + [{^key, score_list}] -> score_list + _ -> [{0, "existing"}] + end + + output = "\n #{key} has #{total_score(scores)} points for the following reasons:\n" + Enum.reduce(scores, output, fn {v, reason}, acc -> + acc <> "\t * #{v} for #{reason} \n" + end) + end - {:reply, total_score(new_scores), Map.put(names, name, new_scores)} + {:reply, formatted, score_table} end - def handle_call({:score, name}, _from, names) do - state = Map.get(names, name, [{0, ""}]) - {:reply, state, names} + def terminate(:shutdown, score_table) do + :dets.close(score_table) + {:noreply, score_table} end - def handle_call({:scores}, _from, names) do - {:reply, names, names} + def handle_info(_msg, score_table) do + {:noreply, score_table} end end diff --git a/mix.exs b/mix.exs index 31f5751..ac6f731 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule BruceHedwig.Mixfile do def project do [app: :bruce_hedwig, - version: "0.0.1", + version: "0.0.3", elixir: "~> 1.2", build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, @@ -30,7 +30,7 @@ defmodule BruceHedwig.Mixfile do defp deps do [ {:hedwig, github: "hedwig-im/hedwig"}, - {:hedwig_flowdock, "~> 0.1.1"}, + {:hedwig_flowdock, "~> 0.1.2"}, {:httpoison, "~> 0.9.0"}, {:exrm, "~> 1.0"}, ] diff --git a/mix.lock b/mix.lock index dbc3bf8..de587c2 100644 --- a/mix.lock +++ b/mix.lock @@ -1,22 +1,22 @@ %{"bbmustache": {:hex, :bbmustache, "1.0.4", "7ba94f971c5afd7b6617918a4bb74705e36cab36eb84b19b6a1b7ee06427aa38", [:rebar], []}, "certifi": {:hex, :certifi, "0.4.0", "a7966efb868b179023618d29a407548f70c52466bf1849b9e8ebd0e34b7ea11f", [:rebar3], []}, "cf": {:hex, :cf, "0.2.1", "69d0b1349fd4d7d4dc55b7f407d29d7a840bf9a1ef5af529f1ebe0ce153fc2ab", [:rebar3], []}, - "connection": {:hex, :connection, "1.0.2", "f4a06dd3ecae4141aa66f94ce92ea4c4b8753069472814932f1cadbc3078ab80", [:mix], []}, + "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []}, "cowlib": {:hex, :cowlib, "1.3.0", "6c80ca7f2863c0d7a21c946312baf473432b56a79e46e20bc27a3bac07c40439", [:make], []}, "erlware_commons": {:hex, :erlware_commons, "0.19.0", "7b43caf2c91950c5f60dc20451e3c3afba44d3d4f7f27bcdc52469285a5a3e70", [:rebar3], [{:cf, "0.2.1", [hex: :cf, optional: false]}]}, "exrm": {:hex, :exrm, "1.0.5", "53ecb20da2f4e5b4c82ea6776824fbc677c8d287bf20efc9fc29cacc2cca124f", [:mix], [{:relx, "~> 3.5", [hex: :relx, optional: false]}]}, "getopt": {:hex, :getopt, "0.8.2", "b17556db683000ba50370b16c0619df1337e7af7ecbf7d64fbf8d1d6bce3109b", [:rebar], []}, "gproc": {:hex, :gproc, "0.5.0", "2df2d886f8f8a7b81a4b04aa17972b5965bbc5bf0100ea6d8e8ac6a0e7389afe", [:rebar], []}, - "gun": {:hex, :gun, "1.0.0-pre.1", "28514327a7572234633e127c1c14c5d91991bc26ec16d3707f05962a31f0c1b5", [:rebar, :make], [{:ranch, "1.1.0", [hex: :ranch, optional: false]}, {:cowlib, "1.3.0", [hex: :cowlib, optional: false]}]}, - "hackney": {:hex, :hackney, "1.6.0", "8d1e9440c9edf23bf5e5e2fe0c71de03eb265103b72901337394c840eec679ac", [:rebar3], [{:ssl_verify_fun, "1.1.0", [hex: :ssl_verify_fun, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:certifi, "0.4.0", [hex: :certifi, optional: false]}]}, - "hedwig": {:git, "https://github.com/hedwig-im/hedwig.git", "2cf3ad2b881bdb81e7b2754d70ba1904e5c7a83e", []}, - "hedwig_flowdock": {:hex, :hedwig_flowdock, "0.1.1", "f5b4cfca92e9502da5027b0f1cbbe2087b54290ad1f61ba5e7a669b8cb8fe728", [:mix], [{:poison, "~> 2.0", [hex: :poison, optional: false]}, {:gun, "1.0.0-pre.1", [hex: :gun, optional: false]}, {:connection, "~> 1.0", [hex: :connection, optional: false]}]}, + "gun": {:hex, :gun, "1.0.0-pre.1", "28514327a7572234633e127c1c14c5d91991bc26ec16d3707f05962a31f0c1b5", [:rebar, :make], [{:cowlib, "1.3.0", [hex: :cowlib, optional: false]}, {:ranch, "1.1.0", [hex: :ranch, optional: false]}]}, + "hackney": {:hex, :hackney, "1.6.0", "8d1e9440c9edf23bf5e5e2fe0c71de03eb265103b72901337394c840eec679ac", [:rebar3], [{:certifi, "0.4.0", [hex: :certifi, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.0", [hex: :ssl_verify_fun, optional: false]}]}, + "hedwig": {:git, "https://github.com/hedwig-im/hedwig.git", "230a4c241fdf38a3eaaaafe8820ce9ddd72482ba", []}, + "hedwig_flowdock": {:hex, :hedwig_flowdock, "0.1.2", "4a5b3b34794233d05b23d1d86e6640642b15ad335c314e4c06ee18e247d54bb8", [:mix], [{:connection, "~> 1.0", [hex: :connection, optional: false]}, {:gun, "1.0.0-pre.1", [hex: :gun, optional: false]}, {:poison, "~> 2.0", [hex: :poison, optional: false]}]}, "httpoison": {:hex, :httpoison, "0.9.0", "68187a2daddfabbe7ca8f7d75ef227f89f0e1507f7eecb67e4536b3c516faddb", [:mix], [{:hackney, "~> 1.6.0", [hex: :hackney, optional: false]}]}, "idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, - "poison": {:hex, :poison, "2.1.0", "f583218ced822675e484648fa26c933d621373f01c6c76bd00005d7bd4b82e27", [:mix], []}, + "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}, "providers": {:hex, :providers, "1.6.0", "db0e2f9043ae60c0155205fcd238d68516331d0e5146155e33d1e79dc452964a", [:rebar3], [{:getopt, "0.8.2", [hex: :getopt, optional: false]}]}, "ranch": {:hex, :ranch, "1.1.0", "f7ed6d97db8c2a27cca85cacbd543558001fc5a355e93a7bff1e9a9065a8545b", [:make], []}, - "relx": {:hex, :relx, "3.19.0", "286dd5244b4786f56aac75d5c8e2d1fb4cfd306810d4ec8548f3ae1b3aadb8f7", [:rebar3], [{:providers, "1.6.0", [hex: :providers, optional: false]}, {:getopt, "0.8.2", [hex: :getopt, optional: false]}, {:erlware_commons, "0.19.0", [hex: :erlware_commons, optional: false]}, {:cf, "0.2.1", [hex: :cf, optional: false]}, {:bbmustache, "1.0.4", [hex: :bbmustache, optional: false]}]}, + "relx": {:hex, :relx, "3.19.0", "286dd5244b4786f56aac75d5c8e2d1fb4cfd306810d4ec8548f3ae1b3aadb8f7", [:rebar3], [{:bbmustache, "1.0.4", [hex: :bbmustache, optional: false]}, {:cf, "0.2.1", [hex: :cf, optional: false]}, {:erlware_commons, "0.19.0", [hex: :erlware_commons, optional: false]}, {:getopt, "0.8.2", [hex: :getopt, optional: false]}, {:providers, "1.6.0", [hex: :providers, optional: false]}]}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:rebar, :make], []}}