Skip to content

Instantly share code, notes, and snippets.

@garthk
Last active October 13, 2020 02:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save garthk/1cd6f930652045892fbf83e829727a5b to your computer and use it in GitHub Desktop.
Save garthk/1cd6f930652045892fbf83e829727a5b to your computer and use it in GitHub Desktop.
Smuggle Terms as Self-Extracting Elixir
defmodule Smuggle do
@moduledoc """
Make it easier to smuggle data from one system to another.
If your target is an `iex` prompt:
Smuggle.dump(value)
# copy
# paste into other iex
Pasting it into GitHub issue, comment, or gist?
Smuggle.dump(value, wrapper: :gfm)
# copy
# paste into GitHub
"""
@typedoc "Encoded terms."
@type encoded :: binary()
@typedoc "Self-extraction code."
@type sfx :: binary()
@doc "Encode a term to a compressed binary in Base64."
@spec encode(term()) :: encoded()
def encode(term) do
term
|> :erlang.term_to_binary()
|> :zlib.gzip()
|> Base.encode64()
|> Stream.unfold(&String.split_at(&1, 76))
|> Enum.take_while(&(&1 != ""))
|> Enum.join("\n")
end
@doc "Decode a compressed binary in Base64."
@spec decode(encoded()) :: term()
def decode(encoded) do
~R/\s+/
|> Regex.replace(encoded, "")
|> Base.decode64!()
|> :zlib.gunzip()
|> :erlang.binary_to_term()
end
@doc "Generate self-extraction code."
@spec gen_sfx(encoded()) :: sfx()
def gen_sfx(encoded) do
encoded = if String.ends_with?(encoded, "\n"), do: encoded, else: encoded <> "\n"
sigil = {
:sigil_S,
[delimiter: "\"\"\"", context: Elixir, import: Kernel],
[{:<<>>, [], [encoded]}, []]
}
quote do
# v = unquote(sigil)
# credo:disable-for-next-line
(fn v ->
~R/\s+/
|> Regex.replace(v, "")
|> Base.decode64!()
|> :zlib.gunzip()
|> :erlang.binary_to_term()
end).(unquote(sigil))
end
# credo:disable-for-next-line
|> Macro.to_string()
end
@doc "Dump self-extracted."
@spec dump(term(), [{:wrapper, atom()}]) :: :ok
def dump(term, opts \\ []) do
{wrapper, opts} = Keyword.pop(opts, :wrapper)
for {k, _} <- opts, do: raise(ArgumentError, "no such option: #{k}")
term |> encode() |> gen_sfx() |> wrap(wrapper) |> IO.puts()
end
defp wrap(sfx, wrapper) when is_atom(wrapper), do: wrap(sfx, {__MODULE__, wrapper, []})
defp wrap(sfx, {m, f, args}), do: Kernel.apply(m, f, [sfx | args])
@doc "No-op wrapper."
@spec noop(sfx()) :: sfx()
def noop(sfx), do: sfx
@doc "Wrap in a details tag for GitHub-flavoured markdown."
@spec gfm(sfx()) :: binary()
def gfm(sfx) do
IO.iodata_to_binary([
~S"""
<details>
<summary>
Self-extracting gzip base64 to paste at the iex prompt:
</summary>
```elixir
""",
sfx,
"\n",
~S"""
```
</details>
"""
])
end
end
@garthk
Copy link
Author

garthk commented Jul 28, 2020

Example, smuggling nil:

Smuggle.dump(nil, wrapper: :gfm)

Result when pasted into this comment:

Self-extracting gzip base64 to paste at the iex prompt:
(fn v -> ~R/\s+/ |> Regex.replace(v, "") |> Base.decode64!() |> :zlib.gunzip() |> :erlang.binary_to_term() end).(~S"""
H4sIAAAAAAAAE2tOYWDOy8wBAATWMBcHAAAA
""")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment