Skip to content

Commit

Permalink
Interpolates messages with variables (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
hugobarauna authored Mar 17, 2023
1 parent 1c8f123 commit cadccbd
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 2 deletions.
5 changes: 5 additions & 0 deletions lib/assets/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export function init(ctx, payload) {
<a href="https://api.slack.com/tutorials/tracks/getting-a-token" target="_blank">create a Slack app and get your app's token</a>.
</p>
</div>
<div class="section">
<p>
To dynamically inject values into the query use double curly braces, like {{name}}.
</p>
</div>
</div>
<div class="row">
<div class="field grow">
Expand Down
5 changes: 3 additions & 2 deletions lib/kino_slack/message_cell.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ defmodule KinoSlack.MessageCell do
@impl true
def to_source(attrs) do
required_fields = ~w(token_secret_name channel message)
message_ast = KinoSlack.MessageInterpolator.interpolate(attrs["message"])

if all_fields_filled?(attrs, required_fields) do
quote do
Expand All @@ -63,7 +64,7 @@ defmodule KinoSlack.MessageCell do
url: "/chat.postMessage",
json: %{
channel: unquote(attrs["channel"]),
text: unquote(attrs["message"])
text: unquote(message_ast)
}
)

Expand All @@ -78,7 +79,7 @@ defmodule KinoSlack.MessageCell do
end
end

def all_fields_filled?(attrs, keys) do
defp all_fields_filled?(attrs, keys) do
Enum.all?(keys, fn key -> attrs[key] not in [nil, ""] end)
end
end
45 changes: 45 additions & 0 deletions lib/kino_slack/message_interpolator.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
defmodule KinoSlack.MessageInterpolator do
@moduledoc false

def interpolate(message) do
args = build_interpolation_args(message, "", [])
args = Enum.reverse(args)
{:<<>>, [], args}
end

defp build_interpolation_args("", buffer, acc) do
prepend_buffer(buffer, acc)
end

defp build_interpolation_args("{{" <> rest, buffer, acc) do
with [inner, rest] <- String.split(rest, "}}", parts: 2),
{:ok, expression} <- Code.string_to_quoted(inner) do
acc = prepend_buffer(buffer, acc)
acc = prepend_interpolation(expression, acc)
build_interpolation_args(rest, "", acc)
else
_ ->
build_interpolation_args(rest, <<buffer::binary, "{{">>, acc)
end
end

defp build_interpolation_args(<<char, rest::binary>>, buffer, acc) do
build_interpolation_args(rest, <<buffer::binary, char>>, acc)
end

defp prepend_interpolation(expression, acc) do
interpolation_node = {
:"::",
[],
[
{{:., [], [Kernel, :to_string]}, [], [expression]},
{:binary, [], Elixir}
]
}

[interpolation_node | acc]
end

defp prepend_buffer("", acc), do: acc
defp prepend_buffer(buffer, acc), do: [buffer | acc]
end
41 changes: 41 additions & 0 deletions test/kino_slack/message_cell_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,47 @@ defmodule KinoSlack.MessageCellTest do
assert generated_code == expected_code
end

test "generates source code with variable interpolation" do
{kino, _source} = start_smart_cell!(MessageCell, %{})

push_event(kino, "update_token_secret_name", "SLACK_TOKEN")
push_event(kino, "update_channel", "#slack-channel")
push_event(kino, "update_message", "Hello {{first_name}} {{last_name}}!")

assert_smart_cell_update(
kino,
%{
"token_secret_name" => "SLACK_TOKEN",
"channel" => "#slack-channel",
"message" => "Hello {{first_name}} {{last_name}}!"
},
generated_code
)

expected_code = ~S"""
req =
Req.new(
base_url: "https://slack.com/api",
auth: {:bearer, System.fetch_env!("LB_SLACK_TOKEN")}
)
response =
Req.post!(req,
url: "/chat.postMessage",
json: %{channel: "#slack-channel", text: "Hello #{first_name} #{last_name}!"}
)
case response.body do
%{"ok" => true} -> :ok
%{"ok" => false, "error" => error} -> {:error, error}
end
"""

expected_code = String.trim(expected_code)

assert generated_code == expected_code
end

test "generates source code from stored attributes" do
stored_attrs = %{
"token_secret_name" => "SLACK_TOKEN",
Expand Down
53 changes: 53 additions & 0 deletions test/kino_slack/messsage_interpolator_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
defmodule KinoSlack.MesssageInterpolatorTest do
use ExUnit.Case, async: true

alias KinoSlack.MessageInterpolator, as: Interpolator

test "it interpolates variables inside a message" do
first_name = "Hugo"
last_name = "Baraúna"
message = "Hi {{first_name}} {{last_name}}! 🎉"

interpolated_ast = Interpolator.interpolate(message)
generated_code = Macro.to_string(interpolated_ast)
{interpolated_message, _} = Code.eval_quoted(interpolated_ast, binding())

assert generated_code == ~S/"Hi #{first_name} #{last_name}! 🎉"/
assert interpolated_message == "Hi Hugo Baraúna! 🎉"
end

test "it interpolates expressons inside a message" do
message = "One plus one is: {{1 + 1}}"

interpolated_ast = Interpolator.interpolate(message)
generated_code = Macro.to_string(interpolated_ast)
{interpolated_message, _} = Code.eval_quoted(interpolated_ast, binding())

assert generated_code == ~S/"One plus one is: #{1 + 1}"/
assert interpolated_message == "One plus one is: 2"
end

test "it interpolates funtion calls inside a message" do
sum = fn a, b -> a + b end
message = "1 + 1 is: {{sum.(1, 1)}}"

interpolated_ast = Interpolator.interpolate(message)
generated_code = Macro.to_string(interpolated_ast)
{interpolated_message, _} = Code.eval_quoted(interpolated_ast, binding())

assert generated_code == ~S/"1 + 1 is: #{sum.(1, 1)}"/
assert interpolated_message == "1 + 1 is: 2"
end

test "it handles messages with only the beginning of interpolation syntax" do
first_name = "Hugo"
message = "hi {{ {{first_name}}"

interpolated_ast = Interpolator.interpolate(message)
generated_code = Macro.to_string(interpolated_ast)
{interpolated_message, _} = Code.eval_quoted(interpolated_ast, binding())

assert generated_code == ~S/"hi {{ #{first_name}"/
assert interpolated_message == "hi {{ Hugo"
end
end

0 comments on commit cadccbd

Please sign in to comment.