Current section
Files
Jump to
Current section
Files
lib/mix/tasks/swoosh.gallery.html.ex
defmodule Mix.Tasks.Swoosh.Gallery.Html do
@moduledoc """
Generate static files from a `Swoosh.Gallery` module that can be distributed and
hosted without your application's code, on places like Amazon S3 or GitHub/GitLab Pages.
## Example
mix swoosh.gallery.html --gallery Sample.Mailer.Gallery --path /tmp/emails
* creating /tmp/emails
* creating /tmp/emails/index.html
* creating /tmp/emails/notifications.new_follower.html
* creating /tmp/emails/notifications.new_follower/preview.html
* creating /tmp/emails/notifications.new_message.html
* creating /tmp/emails/notifications.new_message/preview.html
* creating /tmp/emails/email_with_attachment.html
* creating /tmp/emails/email_with_attachment/preview.html
* creating /tmp/emails/email_with_attachment/attachments/0/file.pdf
* creating /tmp/emails/welcome_users.html
* creating /tmp/emails/welcome_users/preview.html
## Command line options
* `-g`, `--gallery` - the gallery that will be used
* `-p`, `--path` - path where the static files should be generated
"""
use Mix.Task
require Mix.Generator
alias Swoosh.Gallery
alias Swoosh.Gallery.Layout
defmodule Options do
@moduledoc false
defstruct gallery: nil, path: nil
end
@impl Mix.Task
def run(argv) do
Mix.Task.run("app.start")
opts = parse_options(argv)
generate_html(opts)
end
defp parse_options(argv) do
parse_options = [strict: [gallery: :string, path: :string]]
{opts, _args, _} = OptionParser.parse(argv, parse_options)
%Options{
gallery: ensure_gallery!(opts),
path: ensure_path!(opts)
}
end
defp ensure_gallery!(opts) do
if gallery = Keyword.get(opts, :gallery) do
gallery
|> List.wrap()
|> Module.concat()
|> Code.ensure_compiled!()
|> tap(fn mod ->
unless function_exported?(mod, :get, 0) do
Mix.raise("""
The module #{inspect(mod)} is not a valid gallery. Make sure it uses Swoosh.Gallery:
defmodule #{inspect(mod)} do
use Swoosh.Gallery
// preview(...)
end
""")
end
end)
|> then(& &1.get())
|> tap(&ensure_required_functions!(&1))
else
Mix.raise("No gallery available. Please pass a gallery with the --gallery option")
end
end
defp ensure_required_functions!(%{previews: previews}) when is_list(previews) do
Enum.each(previews, fn %{email_mfa: {module, _fun, _args}} ->
ensure_required_functions!(module)
end)
end
defp ensure_required_functions!(module) do
Code.ensure_compiled!(module)
unless function_exported?(module, :preview, 0) do
raise """
The preview/3 function expected #{inspect(module)} to declare a function `preview/0`, but it is missing.
Make sure it is implemented:
def preview do
// return Swoosh.Email.t
end
"""
end
unless function_exported?(module, :preview_details, 0) do
raise """
The preview/3 function expected expected #{inspect(module)} to declare a function `preview_details/0`, but it is missing.
Make sure it is implemented:
def preview_details do
[title: "My Email"]
end
"""
end
end
defp ensure_path!(opts) do
if path = Keyword.get(opts, :path) do
Mix.Generator.create_directory(path)
path
else
Mix.raise("No path available. Please pass a path with the --path option")
end
end
defp generate_html(%{path: destination, gallery: gallery}) do
generate_root(gallery, destination)
for preview <- gallery.previews do
generate_preview(gallery, preview, destination)
end
end
defp generate_root(gallery, destination) do
root = Layout.render(gallery, base_path: "./", preview: nil, format: ".html")
Mix.Generator.create_file(Path.join([destination, "index.html"]), root, force: true)
end
defp generate_preview(gallery, preview, destination) do
preview = Gallery.eval_preview(preview)
preview_path = Path.join([destination, preview.path])
rendered = Layout.render(gallery, base_path: "./", preview: preview, format: ".html")
Mix.Generator.create_file("#{preview_path}.html", rendered, force: true)
generate_html_preview(preview_path, preview.email)
generate_attachments(preview_path, preview)
end
defp generate_html_preview(preview_base_path, email) do
Mix.Generator.create_file(Path.join([preview_base_path, "preview.html"]), email.html_body,
force: true
)
end
defp generate_attachments(preview_base_path, %{email: email} = preview) do
Enum.with_index(email.attachments, fn attachment, index ->
with {:ok, %{data: data}} <- Gallery.read_email_attachment_at(preview, index) do
file_path =
Path.join([
preview_base_path,
"attachments",
Integer.to_string(index),
attachment.filename
])
Mix.Generator.create_file(file_path, data, force: true)
end
end)
end
end