Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for multiple smoothie configurations #4

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 38 additions & 5 deletions README.md
Expand Up @@ -4,7 +4,8 @@ Inline styling and plain text template generation for your email templates.

Follow the installation instructions to set up Smoothie. After that we can use it in the following way in our project:

Let's suppose we are using the excellent Mailgun library to send our emails. Then we set up a Mailer module in the following location: `web/mailers/mailer.ex`, with the following content:
Let's suppose we are using the excellent Mailgun library to send our emails.
Then we set up a Mailer module in the following location: `web/mailers/mailer.ex`, with the following content:

```elixir
defmodule MyApp.Mailer do
Expand Down Expand Up @@ -214,10 +215,19 @@ p, ul, ol {

Now create the template for our email, we can save this in `web/mailers/templates/welcome.html.eex`

Optionally adding additional css styling specific for this template partial is possible using `<style> </style>` tags.

```html
<style>
.inner-template{
font-size: 120%;
color: lightgreen;
}
</style>

<h2>Hi <%= name %>,</h2>

<p>Welcome!</p>
<p class="inner-template">Welcome!</p>

<p>Cheers,</p>

Expand All @@ -231,7 +241,7 @@ defmodule MyApp.Mailer do
# your mailgun config here
@config %{...}
use Mailgun.Client, @config
use Smoothie
use Smoothie, otp_app: MyApp, config: __MODULE__

def welcome_email(user) do
template_params = [
Expand Down Expand Up @@ -285,19 +295,42 @@ Smoothie can be installed as:
3. Specify the locations of your templates, edit `config/confix.exs` in your Elixir project and add the following config:

```elixir
config :smoothie, template_dir: Path.join(["web", "mailers", "templates"])
config :my_app, MyApp.Mailer,
template_dir: Path.join(["web", "mailers", "templates"]),
layout_dir: Path.join(["web", "mailers", "shared_layout"])

config :my_app, MyApp.Mailer.Newsletter,
template_dir: Path.join(["web", "mailers", "newsletters", "templates"]),
layout_dir: Path.join(["web", "mailers", "newsletters", "templates", "layout"])

config :my_app, smoothie_configs: [MyApp.Mailer, MyApp.Mailer.Newsletter]
```

It can also be in any other directory, just provide the correct directory here.

It is really important to make sure this directory exists, otherwise your project will not compile.
Additional layouts can be used by adding additional configurations.

4. The only thing left is install the npm package that smoothie relies on in your project, we can do this with the following command:

```
> mix smoothie.init
```

Compile with layout
```
> mix smoothie.compile
```

Compile without a layout
```
> mix smoothie.compile --no-layout
```

Compile [foundation-emails](https://github.com/zurb/foundation-emails) [inky](https://github.com/zurb/inky) templating format
```
> mix smoothie.compile --no-layout --foundation
```

if you want to do it manually, that's also possible use:

```
Expand Down
25 changes: 23 additions & 2 deletions lib/mix/tasks/compile.ex
Expand Up @@ -2,7 +2,28 @@ defmodule Mix.Tasks.Smoothie.Compile do
use Mix.Task
@shortdoc "Compiles smoothie templates"

def run(_) do
System.cmd Path.join([File.cwd!, "node_modules/.bin/elixir-smoothie"]), [], env: [{"MAIL_TEMPLATE_DIR", Application.get_env(:smoothie, :template_dir)}], into: IO.stream(:stdio, :line)
def run(opts) do

opts = OptionParser.parse(opts)
|> Tuple.to_list()
|> List.first()

use_foundation = opts[:foundation] || :false
use_layout = if opts[:no_layout] == :true, do: :false, else: :true

otp_app = Mix.Project.config[:app]
configs = Application.get_env(otp_app, :smoothie_configs)

for config <- configs do
path = Path.join([File.cwd!, "node_modules/.bin/elixir-smoothie"])
env = [
{"MAIL_TEMPLATE_DIR", Application.get_env(otp_app, config)[:template_dir]},
{"LAYOUT_TEMPLATE_DIR", Application.get_env(otp_app, config)[:layout_dir]},
{"USE_FOUNDATION_EMAILS", Atom.to_string(use_foundation)},
{"USE_LAYOUT", Atom.to_string(use_layout)}
]
System.cmd(path, [], env: env, into: IO.stream(:stdio, :line))
end

end
end
84 changes: 36 additions & 48 deletions lib/smoothie.ex
@@ -1,71 +1,59 @@
defmodule Smoothie do
require EEx

defmacro __using__(_options) do
quote do
import unquote(__MODULE__)
defmacro __using__(opts) do

generate_views()
end
end

# location of the template path
@template_path [Mix.Project.build_path, '..', '..'] ++ [Application.get_env(:smoothie, :template_dir)]

# location of the build path
@build_path @template_path ++ ["build"]
quote bind_quoted: [opts: opts] do

# create the template and build folder at compile time if not exists
unless File.exists?(Path.join(@build_path)), do: File.mkdir_p!(Path.join(@build_path))
@otp_app Keyword.fetch!(opts, :otp_app)
@smoothie_config Keyword.fetch!(opts, :config)
@template_path [Mix.Project.build_path(), '..', '..'] ++ [Application.get_env(@otp_app, @smoothie_config)[:template_dir]]
@build_path @template_path ++ ["build"]
@template_files File.ls!(Path.join(@build_path))
unless File.exists?(Path.join(@build_path)), do: File.mkdir_p!(Path.join(@build_path))

@template_files File.ls!(Path.join(@build_path))
|> Enum.filter(fn(file) -> String.contains?(file, ".eex") end)

# Ensure the macro is recompiled when the templates are changed
@template_files
|> Enum.each(fn(file) ->
@external_resource Path.join(@build_path ++ [file])
end)
# Ensure the macro is recompiled when the templates are changed
Enum.each(@template_files, fn(file) ->
@external_resource Path.join(@build_path ++ [file])
end)

defmacro generate_views do
@template_files
|> Enum.map(fn(file) ->
# read the contents of the template
template_contents = File.read!(Path.join(@build_path ++ [file]))
Enum.each(@template_files, fn(file) ->
# read the contents of the template
@template_contents File.read!(Path.join(@build_path ++ [file]))

# capture variables that are defined in the template
variables =
Regex.scan(~r/<%=[^\w]*(\w+)[^\w]*%>/, template_contents)
# capture variables that are defined in the template
@variables Regex.scan(~r/<%=(.*?)%>/, @template_contents)
|> Enum.map(fn(match) ->
match
|> Enum.at(1)
|> String.to_atom
|> String.trim(" ")
|> String.to_atom()
end)
|> Enum.uniq

# create assignment macro code for in the function block
variable_assignments = variables |> Enum.map(fn(name) ->
quote do
unquote(Macro.var(name, nil)) = args[unquote(name)]
end
end)
# create assignment macro code for in the function block
@variable_assignments Enum.map(@variables, fn(name) ->
quote do
unquote(Macro.var(name, nil)) = args[unquote(name)]
end
end)

# generate function name from file name
template_name =
file
# generate function name from file name
@template_name file
|> String.replace(".eex", "")
|> String.replace(".html", "_html")
|> String.replace(".txt", "_text")
|> String.to_atom

compiled = EEx.compile_string(template_contents, [])
@compiled EEx.compile_string(@template_contents, [])

quote do
def unquote(template_name)(args) do
unquote(variable_assignments)
unquote(compiled)
def unquote(@template_name)(args) do
unquote(@variable_assignments)
unquote(@compiled)
end
end
end)
end)
end
end
end


end
3 changes: 2 additions & 1 deletion mix.exs
Expand Up @@ -23,7 +23,8 @@ defmodule Smoothie.Mixfile do
[
applications: [:logger],
env: [
template_dir: Path.join(["web", "mailers", "templates"])
template_dir: Path.join(["web", "mailers", "templates"]),
layout_dir: Path.join(["web", "mailers", "templates", "layout"])
]
]
end
Expand Down