From 216dbd1281dd6238d05e3c0b91168c92f2c37c96 Mon Sep 17 00:00:00 2001 From: May Matyi Date: Tue, 28 Nov 2023 16:48:41 -0800 Subject: [PATCH] Prevent crash when native layouts are undefined --- lib/live_view_native/layouts.ex | 96 +++++++++++++++++++++++-------- lib/live_view_native/templates.ex | 7 ++- mix.exs | 2 +- mix.lock | 6 +- 4 files changed, 80 insertions(+), 31 deletions(-) diff --git a/lib/live_view_native/layouts.ex b/lib/live_view_native/layouts.ex index f435d04b..e361e34d 100644 --- a/lib/live_view_native/layouts.ex +++ b/lib/live_view_native/layouts.ex @@ -1,5 +1,17 @@ defmodule LiveViewNative.Layouts do - def extract_layouts({:embed_templates, _meta, [template | _args]}, %{} = opts) do + def extract_layouts(%{file: file} = opts) do + file + |> File.read!() + |> Code.string_to_quoted!() + |> extract_layouts_recursive(opts) + |> List.flatten() + |> Enum.map(fn layout_params -> {layout_params.render_function, layout_params} end) + |> Enum.reject(&(format_excluded?(&1, opts))) + |> Enum.into(%{}) + |> apply_default_layouts(opts) + end + + def extract_layouts_recursive({:embed_templates, _meta, [template | _args]}, %{} = opts) do base_path = Path.join([opts.dirname, Path.dirname(template)]) opts = Map.put(opts, :base_path, base_path) @@ -9,15 +21,15 @@ defmodule LiveViewNative.Layouts do |> Enum.filter(& &1) end - def extract_layouts({_func, _meta, [_ | _] = nodes}, %{} = opts), - do: Enum.map(nodes, &extract_layouts(&1, opts)) + def extract_layouts_recursive({_func, _meta, [_ | _] = nodes}, %{} = opts), + do: Enum.map(nodes, &extract_layouts_recursive(&1, opts)) - def extract_layouts([do: {:__block__, [], args}], %{} = opts), do: extract_layouts(args, opts) + def extract_layouts_recursive([do: {:__block__, [], args}], %{} = opts), do: extract_layouts_recursive(args, opts) - def extract_layouts([_ | _] = nodes, %{} = opts), - do: Enum.map(nodes, &extract_layouts(&1, opts)) + def extract_layouts_recursive([_ | _] = nodes, %{} = opts), + do: Enum.map(nodes, &extract_layouts_recursive(&1, opts)) - def extract_layouts(_node, _opts), do: [] + def extract_layouts_recursive(_node, _opts), do: [] def extract_layout(filename, %{platforms: platforms} = opts) do template_path = Path.join(opts.base_path, filename) @@ -27,7 +39,7 @@ defmodule LiveViewNative.Layouts do |> compile_layout(template_path, opts) end - def compile_layout({format, platform}, template_path, _opts) when format != "html" do + def compile_layout({_format, platform}, template_path, _opts) do render_function_name = template_path |> Path.basename() @@ -57,40 +69,76 @@ defmodule LiveViewNative.Layouts do end end + ### + + defp apply_default_layouts(%{} = layouts, %{default_layouts: true, platforms: platforms} = opts) do + platforms + |> Enum.reject(&(format_excluded?(&1, opts))) + |> Enum.flat_map(fn {format, %{default_layouts: %{} = default_layouts} = platform} -> + Enum.map(default_layouts, fn {layout_name, layout_source} -> + {String.to_atom("#{layout_name}_#{format}"), {layout_source, platform}} + end) + end) + |> Enum.into(%{}) + |> Enum.reduce(layouts, fn {render_function_name, {layout_source, platform}}, %{} = acc -> + if Map.has_key?(acc, render_function_name) do + acc + else + Map.put(acc, render_function_name, %{ + template: layout_source, + render_function: render_function_name, + template_path: nil, + eex_engine: platform.eex_engine, + platform_id: platform.platform_id, + tag_handler: platform.tag_handler + }) + end + end) + end + + defp apply_default_layouts(%{} = layouts, _opts), do: layouts + + defp format_excluded?({_, %{platform_id: platform_id}}, %{} = opts) do + case opts do + %{exclude: [_ | _] = excluded_formats} -> + platform_id in excluded_formats + + _ -> + false + end + end + defmacro __using__(_opts \\ []) do quote bind_quoted: [caller: Macro.escape(__CALLER__)], location: :keep do use LiveViewNative.Extensions, role: :layouts layout_templates = - __ENV__.file - |> File.read!() - |> Code.string_to_quoted!() - |> LiveViewNative.Layouts.extract_layouts(%{ + %{ caller: caller, + default_layouts: true, dirname: Path.dirname(__ENV__.file), + exclude: [:html], + file: __ENV__.file, platforms: LiveViewNative.platforms() - }) - |> List.flatten() - |> Enum.map(fn %{} = layout_params -> - @external_resource layout_params.template_path + } + |> LiveViewNative.Layouts.extract_layouts() + |> Enum.map(fn {render_func, %{} = layout_params} -> + if layout_params.template_path do + @external_resource layout_params.template_path + end eex_opts = [ caller: caller, engine: layout_params.eex_engine, file: __ENV__.file, - render_function: {layout_params.render_function, 1}, + render_function: {render_func, 1}, source: layout_params.template, tag_handler: layout_params.tag_handler ] LiveViewNative.Templates.compile_class_tree(layout_params.template, layout_params.platform_id, eex_opts) + expr = LiveViewNative.Templates.with_stylesheet_wrapper(layout_params.template) - EEx.function_from_string( - :def, - layout_params.render_function, - LiveViewNative.Templates.with_stylesheet_wrapper(layout_params.template), - [:assigns], - eex_opts - ) + EEx.function_from_string(:def, render_func, expr, [:assigns], eex_opts) end) end end diff --git a/lib/live_view_native/templates.ex b/lib/live_view_native/templates.ex index 322eaedb..49456b57 100644 --- a/lib/live_view_native/templates.ex +++ b/lib/live_view_native/templates.ex @@ -15,8 +15,9 @@ defmodule LiveViewNative.Templates do end def compile_class_tree(expr, platform_id, eex_opts) do - with %Macro.Env{module: template_module} <- eex_opts[:caller], - %Meeseeks.Document{} = doc <- Meeseeks.parse(expr, :html), + %Macro.Env{module: template_module} = eex_opts[:caller] + + with %Meeseeks.Document{} = doc <- Meeseeks.parse(expr, :html), [_ | _] = class_names <- extract_all_class_names(doc), %{} = class_tree_context <- class_tree_context(platform_id, template_module), %{} = class_tree <- build_class_tree(class_tree_context, class_names, eex_opts) @@ -24,7 +25,7 @@ defmodule LiveViewNative.Templates do dump_class_tree_bytecode(class_tree, template_module) else _fallback -> - # TODO: Generate fallback stylesheet module + dump_class_tree_bytecode(%{}, template_module) :skipped end diff --git a/mix.exs b/mix.exs index 568fe805..9751327e 100644 --- a/mix.exs +++ b/mix.exs @@ -50,7 +50,7 @@ defmodule LiveViewNative.MixProject do {:makeup_eex, ">= 0.1.1", only: :dev, runtime: false}, {:dialyxir, "~> 1.0", only: :dev, runtime: false}, {:meeseeks, "~> 0.17.0"}, - {:live_view_native_platform, "0.2.0-beta.0"} + {:live_view_native_platform, "0.2.0-beta.2"} ] end diff --git a/mix.lock b/mix.lock index 7c4762a2..daf718dd 100644 --- a/mix.lock +++ b/mix.lock @@ -6,12 +6,12 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, "earmark_parser": {:hex, :earmark_parser, "1.4.37", "2ad73550e27c8946648b06905a57e4d454e4d7229c2dafa72a0348c99d8be5f7", [:mix], [], "hexpm", "6b19783f2802f039806f375610faa22da130b8edc21209d0bff47918bb48360e"}, - "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, + "ecto": {:hex, :ecto, "3.11.0", "ff8614b4e70a774f9d39af809c426def80852048440e8785d93a6e91f48fec00", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7769dad267ef967310d6e988e92d772659b11b09a0c015f101ce0fff81ce1f81"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, "floki": {:hex, :floki, "0.34.3", "5e2dcaec5d7c228ce5b1d3501502e308b2d79eb655e4191751a1fe491c37feac", [:mix], [], "hexpm", "9577440eea5b97924b4bf3c7ea55f7b8b6dce589f9b28b096cc294a8dc342341"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "live_view_native_platform": {:hex, :live_view_native_platform, "0.2.0-beta.0", "ea900fecb30f4a286609034ba0b58290b4d553b1cfdabdb24d3b57154a346220", [:mix], [{:ecto, "~> 3.8", [hex: :ecto, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "e4b1bc28adc3234199a30a1d36d4f36d54c078aabaf6c725c372a8ddcc927303"}, + "live_view_native_platform": {:hex, :live_view_native_platform, "0.2.0-beta.2", "8cd8eb8d18e2250bc497f1e0a5338430783ec97bf9f64b148df4bfb267c32ec4", [:mix], [{:ecto, "~> 3.8", [hex: :ecto, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "2ee4626999348e643736e5993aa6f1458754e4c73b58b84197f2b446eb48ce50"}, "live_view_native_stylesheet": {:git, "https://github.com/liveview-native/live_view_native_stylesheet", "4ec329ebcbe81adacda7d864f2e2e406b42cb2ef", [branch: "main"]}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_eex": {:hex, :makeup_eex, "0.1.1", "89352d5da318d97ae27bbcc87201f274504d2b71ede58ca366af6a5fbed9508d", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.16", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_html, "~> 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d111a0994eaaab09ef1a4b3b313ef806513bb4652152c26c0d7ca2be8402a964"}, @@ -28,7 +28,7 @@ "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"}, "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, - "plug": {:hex, :plug, "1.15.1", "b7efd81c1a1286f13efb3f769de343236bd8b7d23b4a9f40d3002fc39ad8f74c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "459497bd94d041d98d948054ec6c0b76feacd28eec38b219ca04c0de13c79d30"}, + "plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},