Skip to content

Commit

Permalink
Merge pull request #225 from liveview-native/bc-template-engine
Browse files Browse the repository at this point in the history
Introduce LiveViewNative.Template.Engine
  • Loading branch information
bcardarella authored Jan 4, 2025
2 parents 2b50aad + c4080bf commit 52494ec
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 224 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- :interface- special attribute support in tags
- async_result/1
- render_upload support in LiveViewNativeTest
- suppport single quotes to wrap attribute values in template parser
- support single quotes to wrap attribute values in template parser
- LVN Commands
- LiveViewNative.Template.Engine

### Changed

- `LiveViewNative.Component` no longer imports `Phoenix.Component.to_form/2`
- `LiveViewNative.LiveView` now requires the `dispatch_to` function to determine which module will be used for rendering
- Migrated many functions out of LiveViewNative.TagEngine to LiveViewNative.Template.Engine

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ config :phoenix_template, format_encoders: [
]

config :phoenix, template_engines: [
neex: LiveViewNative.Engine
neex: LiveViewNative.Template.Engine
]

config :live_view_native_test_endpoint,
Expand Down
4 changes: 2 additions & 2 deletions lib/live_view_native/component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,13 @@ defmodule LiveViewNative.Component do
end

options = [
engine: LiveViewNative.TagEngine, # Phoenix.LiveView.TagEngine,
engine: LiveViewNative.TagEngine,
file: __CALLER__.file,
line: __CALLER__.line + 1,
caller: __CALLER__,
indentation: meta[:indentation] || 0,
source: expr,
tag_handler: LiveViewNative.TagEngine
tag_handler: LiveViewNative.Template.Engine
]

EEx.compile_string(expr, options)
Expand Down
29 changes: 9 additions & 20 deletions lib/live_view_native/engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,19 @@ defmodule LiveViewNative.Engine do
@behaviour Phoenix.Template.Engine

@impl true
def compile(path, _name) do
quote do
require LiveViewNative.Engine
LiveViewNative.Engine.compile(unquote(path))
end
end
def compile(path, name) do
IO.warn("""
LiveViewNative.Engine has been deprecatd in favor of LiveViewNative.Template.Engine.
In config/config.exs update config :phoenix, :template_engines
@doc false
defmacro compile(path) do
trim = Application.get_env(:phoenix, :trim_on_html_eex_engine, true)
source = File.read!(path)
- neex: LiveViewNative.Engine
+ neex: LiveViewNative.Template.Engine
""")

EEx.compile_string(source,
engine: Phoenix.LiveView.TagEngine,
line: 1,
file: path,
trim: trim,
caller: __CALLER__,
source: source,
tag_handler: LiveViewNative.TagEngine
)
LiveViewNative.Template.Engine.compile(path, name)
end

@doc """
@doc """
Encodes the HTML templates to iodata.
"""
def encode_to_iodata!({:safe, body}), do: body
Expand Down
183 changes: 0 additions & 183 deletions lib/live_view_native/tag_engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ defmodule LiveViewNative.TagEngine do
"""

alias Phoenix.LiveView.Tokenizer
@behaviour Phoenix.LiveView.TagEngine
@behaviour EEx.Engine

@impl true
Expand Down Expand Up @@ -79,186 +78,4 @@ defmodule LiveViewNative.TagEngine do
def handle_end(state) do
Phoenix.LiveView.TagEngine.handle_end(state)
end

@doc false
@impl true
def handle_attributes(ast, meta) do
if is_list(ast) and literal_keys?(ast) do
attrs =
Enum.map(ast, fn {key, value} ->
name = to_string(key)

case handle_attr_escape(name, value, meta) do
:error -> handle_attrs_escape([{safe_unless_special(name), value}], meta)
parts -> {name, parts}
end
end)

{:attributes, attrs}
else
{:quoted, handle_attrs_escape(ast, meta)}
end
end

@doc false
@impl true
defdelegate annotate_body(caller), to: Phoenix.LiveView.HTMLEngine

@doc false
@impl true
defdelegate annotate_caller(file, line), to: Phoenix.LiveView.HTMLEngine

@doc false
@impl true
def classify_type(":inner_block"), do: {:error, "the slot name :inner_block is reserved"}
def classify_type(":" <> name), do: {:slot, name}

def classify_type(<<first, _::binary>> = name) when first in ?A..?Z do
if String.contains?(name, ".") do
{:remote_component, name}
else
{:tag, name}
end
end

def classify_type("." <> name),
do: {:local_component, name}

def classify_type(name), do: {:tag, name}

defp literal_keys?([{key, _value} | rest]) when is_atom(key) or is_binary(key),
do: literal_keys?(rest)

defp literal_keys?([]), do: true
defp literal_keys?(_other), do: false

defp handle_attrs_escape(attrs, meta) do
quote line: meta[:line] do
unquote(__MODULE__).attributes_escape(unquote(attrs))
end
end

defp handle_attr_escape("class", [head | tail], meta) when is_binary(head) do
{bins, tail} = Enum.split_while(tail, &is_binary/1)
encoded = class_attribute_encode([head | bins])

if tail == [] do
[IO.iodata_to_binary(encoded)]
else
tail =
quote line: meta[:line] do
{:safe, unquote(__MODULE__).class_attribute_encode(unquote(tail))}
end

[IO.iodata_to_binary([encoded, ?\s]), tail]
end
end

defp handle_attr_escape("class", value, meta) do
[
quote(
line: meta[:line],
do: {:safe, unquote(__MODULE__).class_attribute_encode(unquote(value))}
)
]
end

defp handle_attr_escape(_name, value, meta) do
case extract_binaries(value, true, [], meta) do
:error -> :error
reversed -> Enum.reverse(reversed)
end
end

defp extract_binaries({:<>, _, [left, right]}, _root?, acc, meta) do
extract_binaries(right, false, extract_binaries(left, false, acc, meta), meta)
end

defp extract_binaries({:<<>>, _, parts} = binary, _root?, acc, meta) do
Enum.reduce(parts, acc, fn
part, acc when is_binary(part) ->
[binary_encode(part) | acc]

{:"::", _, [binary, {:binary, _, _}]}, acc ->
[quoted_binary_encode(binary, meta) | acc]

_, _ ->
throw(:unknown_part)
end)
catch
:unknown_part ->
[quoted_binary_encode(binary, meta) | acc]
end

defp extract_binaries(binary, _root?, acc, _meta) when is_binary(binary),
do: [binary_encode(binary) | acc]

defp extract_binaries(value, false, acc, meta),
do: [quoted_binary_encode(value, meta) | acc]

defp extract_binaries(_value, true, _acc, _meta),
do: :error

@doc false
def attributes_escape(attrs) do
attrs
|> Enum.map(fn
{key, value} when is_atom(key) -> {Atom.to_string(key), value}
other -> other
end)
|> LiveViewNative.Template.attributes_escape()
end

@doc false
def class_attribute_encode(list) when is_list(list),
do: list |> class_attribute_list() |> LiveViewNative.Engine.encode_to_iodata!()

def class_attribute_encode(other),
do: empty_attribute_encode(other)

defp class_attribute_list(value) do
value
|> Enum.flat_map(fn
nil -> []
false -> []
inner when is_list(inner) -> [class_attribute_list(inner)]
other -> [other]
end)
|> Enum.join(" ")
end

@doc false
def empty_attribute_encode(nil), do: ""
def empty_attribute_encode(false), do: ""
def empty_attribute_encode(true), do: ""
def empty_attribute_encode(value), do: LiveViewNative.Engine.encode_to_iodata!(value)

@doc false
def binary_encode(value) when is_binary(value) do
value
|> LiveViewNative.Engine.encode_to_iodata!()
|> IO.iodata_to_binary()
end

def binary_encode(value) do
raise ArgumentError, "expected a binary in <>, got: #{inspect(value)}"
end

defp quoted_binary_encode(binary, meta) do
quote line: meta[:line] do
{:safe, unquote(__MODULE__).binary_encode(unquote(binary))}
end
end

# We mark attributes as safe so we don't escape them
# at rendering time. However, some attributes are
# specially handled, so we keep them as strings shape.
defp safe_unless_special("aria"), do: :aria
defp safe_unless_special("class"), do: :class
defp safe_unless_special("style"), do: :style
defp safe_unless_special(name), do: {:safe, name}

@doc false
@impl true
def void?(_), do: false
end
Loading

0 comments on commit 52494ec

Please sign in to comment.