diff --git a/CHANGELOG.md b/CHANGELOG.md
index 77a230f..c3832c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
## Master
+## 0.6.0-alpha.2
+
+### Component API
+
+Please see the README for more details regarding the Component API
+
## 0.6.0-alpha.1
### Generators
diff --git a/README.md b/README.md
index 589a242..289dafe 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,64 @@ temple do
end
```
+### Components
+
+To define a component, you can create a file in your configured temple
+components directory, which defaults to `lib/components`. You would
+probably want to change that to be `lib/my_app_web/components` if you
+are building a phoenix app.
+
+```elixir
+# config/config.exs
+
+config :temple, :components_path, "./lib/my_app_web/components"
+```
+
+This file should be of the `.exs` extension.
+
+You can then use this component in any other temple template.
+
+For example, if I were to define a `flex` component, I would create a
+file called `lib/my_app_web/components/flex.exs`, with the following
+contents.
+
+```elixir
+div class: "flex #{@temple[:class]}", id: @id do
+ @children
+end
+```
+
+And we could use the component like so
+
+```elixir
+flex class: "justify-between items-center", id: "arnold" do
+ div do: "Hi"
+ div do: "I'm"
+ div do: "Arnold"
+ div do: "Schwarzenegger"
+end
+```
+
+We've demonstrated several features to components in this example.
+
+We can pass assigns to our component, and access them just like we would in a normal phoenix template. If they don't match up with any assigns we passed to our component, they will be rendered as-is, and will become a normal Phoenix assign.
+
+You can also access a special `@temple` assign. This allows you do optionally pass an assign, and not have the `@my_assign` pass through. If you didn't pass it to your component, it will evaluate to nil.
+
+The block passed to your component can be accessed as `@children`. This allows your components to wrap a body of markup from the call site.
+
+In order for components to trigger a recompile when they are changed, you can call `use Temple.Recompiler` in your `lib/my_app_web.ex` file, in the `view`, `live_view`, and `live_component` functions
+
+```elixir
+def view do
+ quote do
+ # ...
+ use Temple.Recompiler
+ # ...
+ end
+end
+```
+
### Phoenix templates
Add the templating engine to your Phoenix configuration.
diff --git a/config/config.exs b/config/config.exs
index d2d855e..8233fe9 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -1 +1,3 @@
use Mix.Config
+
+import_config "#{Mix.env()}.exs"
diff --git a/config/dev.exs b/config/dev.exs
new file mode 100644
index 0000000..d2d855e
--- /dev/null
+++ b/config/dev.exs
@@ -0,0 +1 @@
+use Mix.Config
diff --git a/config/test.exs b/config/test.exs
new file mode 100644
index 0000000..ca31719
--- /dev/null
+++ b/config/test.exs
@@ -0,0 +1,3 @@
+use Mix.Config
+
+config :temple, components_path: "./test/support/components"
diff --git a/lib/mix/tasks/temple.gen.live.ex b/lib/mix/tasks/temple.gen.live.ex
index 031e4ad..c68cb46 100644
--- a/lib/mix/tasks/temple.gen.live.ex
+++ b/lib/mix/tasks/temple.gen.live.ex
@@ -152,10 +152,10 @@ if Code.ensure_loaded?(Mix.Phoenix) do
end
defp copy_new_files(%Context{} = context, binding, paths) do
- files = files_to_be_generated(context) |> IO.inspect(label: "FILES")
+ files = files_to_be_generated(context)
Mix.Phoenix.copy_from(
- paths |> IO.inspect(label: "PATHS"),
+ paths,
"priv/templates/temple.gen.live",
binding,
files
diff --git a/lib/temple.ex b/lib/temple.ex
index 6a09900..74be0a3 100644
--- a/lib/temple.ex
+++ b/lib/temple.ex
@@ -122,266 +122,11 @@ defmodule Temple do
end
end
- defmodule Private do
- @moduledoc false
- @aliases Application.get_env(:temple, :aliases, [])
-
- @nonvoid_elements ~w[
- head title style script
- noscript template
- body section nav article aside h1 h2 h3 h4 h5 h6
- header footer address main
- p pre blockquote ol ul li dl dt dd figure figcaption div
- a em strong small s cite q dfn abbr data time code var samp kbd
- sub sup i b u mark ruby rt rp bdi bdo span
- ins del
- iframe object video audio canvas
- map
- table caption colgroup tbody thead tfoot tr td th
- form fieldset legend label button select datalist optgroup
- option textarea output progress meter
- details summary menuitem menu
- html
- ]a
-
- @nonvoid_elements_aliases Enum.map(@nonvoid_elements, fn el ->
- Keyword.get(@aliases, el, el)
- end)
- @nonvoid_elements_lookup Enum.map(@nonvoid_elements, fn el ->
- {Keyword.get(@aliases, el, el), el}
- end)
-
- @void_elements ~w[
- meta link base
- area br col embed hr img input keygen param source track wbr
- ]a
-
- @void_elements_aliases Enum.map(@void_elements, fn el -> Keyword.get(@aliases, el, el) end)
- @void_elements_lookup Enum.map(@void_elements, fn el ->
- {Keyword.get(@aliases, el, el), el}
- end)
-
- def snake_to_kebab(stringable),
- do:
- stringable |> to_string() |> String.replace_trailing("_", "") |> String.replace("_", "-")
-
- def kebab_to_snake(stringable),
- do: stringable |> to_string() |> String.replace("-", "_")
-
- def compile_attrs([]), do: ""
-
- def compile_attrs([attrs]) when is_list(attrs) do
- compile_attrs(attrs)
- end
-
- def compile_attrs(attrs) do
- for {name, value} <- attrs, into: "" do
- name = snake_to_kebab(name)
-
- case value do
- {_, _, _} = macro ->
- " " <> name <> "=\"<%= " <> Macro.to_string(macro) <> " %>\""
-
- value ->
- " " <> name <> "=\"" <> to_string(value) <> "\""
- end
- end
- end
-
- def split_args(nil), do: {[], []}
-
- def split_args(args) do
- {do_and_else, args} =
- args
- |> Enum.split_with(fn
- arg when is_list(arg) ->
- (Keyword.keys(arg) -- [:do, :else]) |> Enum.count() == 0
-
- _ ->
- false
- end)
-
- {List.flatten(do_and_else), args}
- end
-
- def split_on_fn([{:fn, _, _} = func | rest], {args, _, args2}) do
- split_on_fn(rest, {args, func, args2})
- end
-
- def split_on_fn([arg | rest], {args, nil, args2}) do
- split_on_fn(rest, {[arg | args], nil, args2})
- end
-
- def split_on_fn([arg | rest], {args, func, args2}) do
- split_on_fn(rest, {args, func, [arg | args2]})
- end
-
- def split_on_fn([], {args, func, args2}) do
- {Enum.reverse(args), func, Enum.reverse(args2)}
- end
-
- def pop_compact?([]), do: {false, []}
- def pop_compact?([args]) when is_list(args), do: pop_compact?(args)
-
- def pop_compact?(args) do
- Keyword.pop(args, :compact, false)
- end
-
- def traverse(buffer, {:__block__, _meta, block}) do
- traverse(buffer, block)
- end
-
- def traverse(buffer, {name, meta, args} = macro) do
- {do_and_else, args} =
- args
- |> split_args()
-
- includes_fn? = args |> Enum.any?(fn x -> match?({:fn, _, _}, x) end)
-
- case name do
- {:., _, [{:__aliases__, _, [:Temple]}, name]} when name in @nonvoid_elements_aliases ->
- {do_and_else, args} =
- case args do
- [args] ->
- {do_value, args} = Keyword.pop(args, :do)
-
- do_and_else = Keyword.put_new(do_and_else, :do, do_value)
-
- {do_and_else, args}
-
- _ ->
- {do_and_else, args}
- end
-
- name = @nonvoid_elements_lookup[name]
-
- {compact?, args} = pop_compact?(args)
-
- Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
- unless compact?, do: Buffer.put(buffer, "\n")
- traverse(buffer, do_and_else[:do])
- if compact?, do: Buffer.remove_new_line(buffer)
- Buffer.put(buffer, "#{name}>")
- Buffer.put(buffer, "\n")
-
- {:., _, [{:__aliases__, _, [:Temple]}, name]} when name in @void_elements_aliases ->
- name = @void_elements_lookup[name]
-
- Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
- Buffer.put(buffer, "\n")
-
- name when name in @nonvoid_elements_aliases ->
- {do_and_else, args} =
- case args do
- [args] ->
- {do_value, args} = Keyword.pop(args, :do)
-
- do_and_else = Keyword.put_new(do_and_else, :do, do_value)
-
- {do_and_else, args}
-
- _ ->
- {do_and_else, args}
- end
-
- name = @nonvoid_elements_lookup[name]
-
- {compact?, args} = pop_compact?(args)
-
- Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
- unless compact?, do: Buffer.put(buffer, "\n")
- traverse(buffer, do_and_else[:do])
- if compact?, do: Buffer.remove_new_line(buffer)
- Buffer.put(buffer, "#{name}>")
- Buffer.put(buffer, "\n")
-
- name when name in @void_elements_aliases ->
- name = @void_elements_lookup[name]
-
- Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
- Buffer.put(buffer, "\n")
-
- name when includes_fn? ->
- {args, func_arg, args2} = split_on_fn(args, {[], nil, []})
-
- {func, _, [{arrow, _, [[{arg, _, _}], block]}]} = func_arg
-
- Buffer.put(
- buffer,
- "<%= " <>
- to_string(name) <>
- " " <>
- (Enum.map(args, &Macro.to_string(&1)) |> Enum.join(", ")) <>
- ", " <>
- to_string(func) <> " " <> to_string(arg) <> " " <> to_string(arrow) <> " %>"
- )
-
- Buffer.put(buffer, "\n")
-
- traverse(buffer, block)
-
- if Enum.any?(args2) do
- Buffer.put(
- buffer,
- "<% end, " <>
- (Enum.map(args2, fn arg -> Macro.to_string(arg) end)
- |> Enum.join(", ")) <> " %>"
- )
-
- Buffer.put(buffer, "\n")
- else
- Buffer.put(buffer, "<% end %>")
- Buffer.put(buffer, "\n")
- end
-
- name when name in [:for, :if, :unless] ->
- Buffer.put(buffer, "<%= " <> Macro.to_string({name, meta, args}) <> " do %>")
- Buffer.put(buffer, "\n")
-
- traverse(buffer, do_and_else[:do])
-
- if Keyword.has_key?(do_and_else, :else) do
- Buffer.put(buffer, "<% else %>")
- Buffer.put(buffer, "\n")
- traverse(buffer, do_and_else[:else])
- end
-
- Buffer.put(buffer, "<% end %>")
- Buffer.put(buffer, "\n")
-
- name when name in [:=] ->
- Buffer.put(buffer, "<% " <> Macro.to_string(macro) <> " %>")
- Buffer.put(buffer, "\n")
- traverse(buffer, do_and_else[:do])
-
- _ ->
- Buffer.put(buffer, "<%= " <> Macro.to_string(macro) <> " %>")
- Buffer.put(buffer, "\n")
- traverse(buffer, do_and_else[:do])
- end
- end
-
- def traverse(buffer, [first | rest]) do
- traverse(buffer, first)
-
- traverse(buffer, rest)
- end
-
- def traverse(buffer, text) when is_binary(text) do
- Buffer.put(buffer, text)
- Buffer.put(buffer, "\n")
- end
-
- def traverse(_buffer, arg) when arg in [nil, []] do
- nil
- end
- end
-
defmacro temple([do: block] = _block) do
{:ok, buffer} = Buffer.start_link()
buffer
- |> Temple.Private.traverse(block)
+ |> Temple.Parser.Private.traverse(block)
markup = Buffer.get(buffer)
@@ -399,7 +144,7 @@ defmodule Temple do
{:ok, buffer} = Buffer.start_link()
buffer
- |> Temple.Private.traverse(unquote(block))
+ |> Temple.Parser.Private.traverse(unquote(block))
markup = Buffer.get(buffer)
@@ -413,7 +158,7 @@ defmodule Temple do
{:ok, buffer} = Buffer.start_link()
buffer
- |> Temple.Private.traverse(block)
+ |> Temple.Parser.Private.traverse(block)
markup = Buffer.get(buffer)
diff --git a/lib/temple/parser.ex b/lib/temple/parser.ex
new file mode 100644
index 0000000..babaf4d
--- /dev/null
+++ b/lib/temple/parser.ex
@@ -0,0 +1,455 @@
+defmodule Temple.Parser do
+ alias Temple.Buffer
+ @components_path Application.get_env(:temple, :components_path, "./lib/components")
+
+ @aliases Application.get_env(:temple, :aliases, [])
+
+ @nonvoid_elements ~w[
+ head title style script
+ noscript template
+ body section nav article aside h1 h2 h3 h4 h5 h6
+ header footer address main
+ p pre blockquote ol ul li dl dt dd figure figcaption div
+ a em strong small s cite q dfn abbr data time code var samp kbd
+ sub sup i b u mark ruby rt rp bdi bdo span
+ ins del
+ iframe object video audio canvas
+ map
+ table caption colgroup tbody thead tfoot tr td th
+ form fieldset legend label button select datalist optgroup
+ option textarea output progress meter
+ details summary menuitem menu
+ html
+ ]a
+
+ @nonvoid_elements_aliases Enum.map(@nonvoid_elements, fn el ->
+ Keyword.get(@aliases, el, el)
+ end)
+ @nonvoid_elements_lookup Enum.map(@nonvoid_elements, fn el ->
+ {Keyword.get(@aliases, el, el), el}
+ end)
+
+ @void_elements ~w[
+ meta link base
+ area br col embed hr img input keygen param source track wbr
+ ]a
+
+ @void_elements_aliases Enum.map(@void_elements, fn el -> Keyword.get(@aliases, el, el) end)
+ @void_elements_lookup Enum.map(@void_elements, fn el ->
+ {Keyword.get(@aliases, el, el), el}
+ end)
+
+ defmodule Private do
+ @moduledoc false
+
+ def snake_to_kebab(stringable),
+ do:
+ stringable |> to_string() |> String.replace_trailing("_", "") |> String.replace("_", "-")
+
+ def kebab_to_snake(stringable),
+ do: stringable |> to_string() |> String.replace("-", "_")
+
+ def compile_attrs([]), do: ""
+
+ def compile_attrs([attrs]) when is_list(attrs) do
+ compile_attrs(attrs)
+ end
+
+ def compile_attrs(attrs) do
+ for {name, value} <- attrs, into: "" do
+ name = snake_to_kebab(name)
+
+ case value do
+ {_, _, _} = macro ->
+ " " <> name <> "=\"<%= " <> Macro.to_string(macro) <> " %>\""
+
+ value ->
+ " " <> name <> "=\"" <> to_string(value) <> "\""
+ end
+ end
+ end
+
+ def split_args(nil), do: {[], []}
+
+ def split_args(args) do
+ {do_and_else, args} =
+ args
+ |> Enum.split_with(fn
+ arg when is_list(arg) ->
+ (Keyword.keys(arg) -- [:do, :else]) |> Enum.count() == 0
+
+ _ ->
+ false
+ end)
+
+ {List.flatten(do_and_else), args}
+ end
+
+ def split_on_fn([{:fn, _, _} = func | rest], {args, _, args2}) do
+ split_on_fn(rest, {args, func, args2})
+ end
+
+ def split_on_fn([arg | rest], {args, nil, args2}) do
+ split_on_fn(rest, {[arg | args], nil, args2})
+ end
+
+ def split_on_fn([arg | rest], {args, func, args2}) do
+ split_on_fn(rest, {args, func, [arg | args2]})
+ end
+
+ def split_on_fn([], {args, func, args2}) do
+ {Enum.reverse(args), func, Enum.reverse(args2)}
+ end
+
+ def pop_compact?([]), do: {false, []}
+ def pop_compact?([args]) when is_list(args), do: pop_compact?(args)
+
+ def pop_compact?(args) do
+ Keyword.pop(args, :compact, false)
+ end
+
+ def traverse(buffer, {:__block__, _meta, block}) do
+ traverse(buffer, block)
+ end
+
+ def traverse(buffer, {_name, _meta, _args} = original_macro) do
+ Temple.Parser.parsers()
+ |> Enum.reduce_while(original_macro, fn parser, macro ->
+ with true <- parser.applicable?.(macro),
+ :ok <- parser.parse.(macro, buffer) do
+ {:halt, macro}
+ else
+ {:component_applied, adjusted_macro} ->
+ traverse(buffer, adjusted_macro)
+
+ {:halt, adjusted_macro}
+
+ false ->
+ {:cont, macro}
+ end
+ end)
+ end
+
+ def traverse(buffer, [first | rest]) do
+ traverse(buffer, first)
+
+ traverse(buffer, rest)
+ end
+
+ def traverse(buffer, text) when is_binary(text) do
+ Buffer.put(buffer, text)
+ Buffer.put(buffer, "\n")
+
+ :ok
+ end
+
+ def traverse(_buffer, arg) when arg in [nil, []] do
+ :ok
+ end
+ end
+
+ def parsers(),
+ do: [
+ %{
+ name: :temple_namespace_nonvoid,
+ applicable?: fn {name, _meta, _args} ->
+ try do
+ {:., _, [{:__aliases__, _, [:Temple]}, name]} = name
+ name in @nonvoid_elements_aliases
+ rescue
+ MatchError ->
+ false
+ end
+ end,
+ parse: fn {name, _meta, args}, buffer ->
+ import Temple.Parser.Private
+ {:., _, [{:__aliases__, _, [:Temple]}, name]} = name
+
+ {do_and_else, args} =
+ args
+ |> split_args()
+
+ {do_and_else, args} =
+ case args do
+ [args] ->
+ {do_value, args} = Keyword.pop(args, :do)
+
+ do_and_else = Keyword.put_new(do_and_else, :do, do_value)
+
+ {do_and_else, args}
+
+ _ ->
+ {do_and_else, args}
+ end
+
+ name = @nonvoid_elements_lookup[name]
+
+ {compact?, args} = pop_compact?(args)
+
+ Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
+ unless compact?, do: Buffer.put(buffer, "\n")
+ traverse(buffer, do_and_else[:do])
+ if compact?, do: Buffer.remove_new_line(buffer)
+ Buffer.put(buffer, "#{name}>")
+ Buffer.put(buffer, "\n")
+ end
+ },
+ %{
+ name: :temple_namespace_void,
+ applicable?: fn {name, _meta, _args} ->
+ try do
+ {:., _, [{:__aliases__, _, [:Temple]}, name]} = name
+ name in @void_elements_aliases
+ rescue
+ MatchError ->
+ false
+ end
+ end,
+ parse: fn {name, _, args}, buffer ->
+ import Temple.Parser.Private
+ {:., _, [{:__aliases__, _, [:Temple]}, name]} = name
+
+ {_do_and_else, args} =
+ args
+ |> split_args()
+
+ name = @void_elements_lookup[name]
+
+ Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
+ Buffer.put(buffer, "\n")
+ end
+ },
+ %{
+ name: :components,
+ applicable?: fn {name, meta, _} ->
+ try do
+ !meta[:temple_component_applied] &&
+ File.exists?(Path.join([@components_path, "#{name}.exs"]))
+ rescue
+ _ ->
+ false
+ end
+ end,
+ parse: fn {name, _meta, args}, _buffer ->
+ import Temple.Parser.Private
+
+ {assigns, children} =
+ case args do
+ [assigns, [do: block]] ->
+ {assigns, block}
+
+ [[do: block]] ->
+ {nil, block}
+
+ [assigns] ->
+ {assigns, nil}
+
+ _ ->
+ {nil, nil}
+ end
+
+ ast =
+ File.read!(Path.join([@components_path, "#{name}.exs"]))
+ |> Code.string_to_quoted!()
+
+ {name, meta, args} =
+ ast
+ |> Macro.prewalk(fn
+ {:@, _, [{:children, _, _}]} ->
+ children
+
+ {:@, _, [{:temple, _, _}]} ->
+ assigns
+
+ {:@, _, [{name, _, _}]} = node ->
+ if !is_nil(assigns) && name in Keyword.keys(assigns) do
+ Keyword.get(assigns, name, nil)
+ else
+ node
+ end
+
+ node ->
+ node
+ end)
+
+ ast =
+ if Enum.any?(
+ [
+ @nonvoid_elements,
+ @nonvoid_elements_aliases,
+ @void_elements,
+ @void_elements_aliases
+ ],
+ fn elements -> name in elements end
+ ) do
+ {name, Keyword.put(meta, :temple_component_applied, true), args}
+ else
+ {name, meta, args}
+ end
+
+ {:component_applied, ast}
+ end
+ },
+ %{
+ name: :nonvoid_elements_aliases,
+ applicable?: fn {name, _, _} ->
+ name in @nonvoid_elements_aliases
+ end,
+ parse: fn {name, _, args}, buffer ->
+ import Temple.Parser.Private
+
+ {do_and_else, args} =
+ args
+ |> split_args()
+
+ {do_and_else, args} =
+ case args do
+ [args] ->
+ {do_value, args} = Keyword.pop(args, :do)
+
+ do_and_else = Keyword.put_new(do_and_else, :do, do_value)
+
+ {do_and_else, args}
+
+ _ ->
+ {do_and_else, args}
+ end
+
+ name = @nonvoid_elements_lookup[name]
+
+ {compact?, args} = pop_compact?(args)
+
+ Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
+ unless compact?, do: Buffer.put(buffer, "\n")
+ traverse(buffer, do_and_else[:do])
+ if compact?, do: Buffer.remove_new_line(buffer)
+ Buffer.put(buffer, "#{name}>")
+ Buffer.put(buffer, "\n")
+ end
+ },
+ %{
+ name: :void_elements_aliases,
+ applicable?: fn {name, _, _} ->
+ name in @void_elements_aliases
+ end,
+ parse: fn {name, _, args}, buffer ->
+ import Temple.Parser.Private
+
+ {_do_and_else, args} =
+ args
+ |> split_args()
+
+ name = @void_elements_lookup[name]
+
+ Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
+ Buffer.put(buffer, "\n")
+ end
+ },
+ %{
+ name: :anonymous_functions,
+ applicable?: fn {_, _, args} ->
+ import Temple.Parser.Private, only: [split_args: 1]
+
+ args |> split_args() |> elem(1) |> Enum.any?(fn x -> match?({:fn, _, _}, x) end)
+ end,
+ parse: fn {name, _, args}, buffer ->
+ import Temple.Parser.Private
+
+ {_do_and_else, args} =
+ args
+ |> split_args()
+
+ {args, func_arg, args2} = split_on_fn(args, {[], nil, []})
+
+ {func, _, [{arrow, _, [[{arg, _, _}], block]}]} = func_arg
+
+ Buffer.put(
+ buffer,
+ "<%= " <>
+ to_string(name) <>
+ " " <>
+ (Enum.map(args, &Macro.to_string(&1)) |> Enum.join(", ")) <>
+ ", " <>
+ to_string(func) <> " " <> to_string(arg) <> " " <> to_string(arrow) <> " %>"
+ )
+
+ Buffer.put(buffer, "\n")
+
+ traverse(buffer, block)
+
+ if Enum.any?(args2) do
+ Buffer.put(
+ buffer,
+ "<% end, " <>
+ (Enum.map(args2, fn arg -> Macro.to_string(arg) end)
+ |> Enum.join(", ")) <> " %>"
+ )
+
+ Buffer.put(buffer, "\n")
+ else
+ Buffer.put(buffer, "<% end %>")
+ Buffer.put(buffer, "\n")
+ end
+ end
+ },
+ %{
+ name: :for_if_unless,
+ applicable?: fn {name, _, _} ->
+ name in [:for, :if, :unless]
+ end,
+ parse: fn {name, meta, args}, buffer ->
+ import Temple.Parser.Private
+
+ {do_and_else, args} =
+ args
+ |> split_args()
+
+ Buffer.put(buffer, "<%= " <> Macro.to_string({name, meta, args}) <> " do %>")
+ Buffer.put(buffer, "\n")
+
+ traverse(buffer, do_and_else[:do])
+
+ if Keyword.has_key?(do_and_else, :else) do
+ Buffer.put(buffer, "<% else %>")
+ Buffer.put(buffer, "\n")
+ traverse(buffer, do_and_else[:else])
+ end
+
+ Buffer.put(buffer, "<% end %>")
+ Buffer.put(buffer, "\n")
+ end
+ },
+ %{
+ name: :match,
+ applicable?: fn {name, _, _} ->
+ name in [:=]
+ end,
+ parse: fn {_, _, args} = macro, buffer ->
+ import Temple.Parser.Private
+
+ {do_and_else, _args} =
+ args
+ |> split_args()
+
+ Buffer.put(buffer, "<% " <> Macro.to_string(macro) <> " %>")
+ Buffer.put(buffer, "\n")
+ traverse(buffer, do_and_else[:do])
+ end
+ },
+ %{
+ name: :default,
+ applicable?: fn _ -> true end,
+ parse: fn {_, _, args} = macro, buffer ->
+ import Temple.Parser.Private
+
+ {do_and_else, _args} =
+ args
+ |> split_args()
+
+ Buffer.put(buffer, "<%= " <> Macro.to_string(macro) <> " %>")
+ Buffer.put(buffer, "\n")
+ traverse(buffer, do_and_else[:do])
+ end
+ }
+ ]
+end
diff --git a/lib/temple/recompiler.ex b/lib/temple/recompiler.ex
new file mode 100644
index 0000000..3a2bff0
--- /dev/null
+++ b/lib/temple/recompiler.ex
@@ -0,0 +1,15 @@
+defmodule Temple.Recompiler do
+ defmacro __using__(_) do
+ quote do
+ component_path = Application.get_env(:temple, :components_path)
+
+ for f <- File.ls!(component_path),
+ do:
+ Module.put_attribute(
+ __MODULE__,
+ :external_resource,
+ Path.join(component_path, f)
+ )
+ end
+ end
+end
diff --git a/test/partial_test.exs b/test/partial_test.exs
new file mode 100644
index 0000000..b7196a8
--- /dev/null
+++ b/test/partial_test.exs
@@ -0,0 +1,16 @@
+defmodule PartialTest do
+ use ExUnit.Case, async: true
+ use Temple
+ use Temple.Support.Utils
+
+ test "can correctly redefine elements" do
+ result =
+ temple do
+ section do
+ "Howdy!"
+ end
+ end
+
+ assert result == ~s{
Bob
\n<%= foo %>
} end + + test "inlines function components" do + result = + temple do + div class: "font-bold" do + "Hello, world" + end + + component do + "I'm a component!" + end + end + + assert result == + ~s{