Skip to content

Commit

Permalink
EEx interpolation (#42)
Browse files Browse the repository at this point in the history
Introduces EEx interpolation on the rules document prior to passing to
the format specific parser. This means there is a two-pahse process
  • Loading branch information
bcardarella authored Jan 20, 2024
1 parent 95cb26f commit 77572c1
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 214 deletions.
63 changes: 30 additions & 33 deletions lib/live_view_native/stylesheet.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,38 @@ defmodule LiveViewNative.Stylesheet do
format: format,
})

case LiveViewNative.Stylesheet.RulesParser.fetch(format) do
{:ok, parser} ->
quote do
import LiveViewNative.Stylesheet.SheetParser, only: [sigil_SHEET: 2]
import LiveViewNative.Stylesheet.RulesHelpers
import LiveViewNative.Component, only: [sigil_LVN: 2]

use unquote(parser)
@format unquote(format)
@before_compile LiveViewNative.Stylesheet

def compile_ast(class_or_list, target \\ [target: :all])
def compile_ast(class_or_list, target: target) do
class_or_list
|> List.wrap()
|> Enum.reduce(%{}, fn(class_name, class_map) ->
case class(class_name, target: target) do
{:unmatched, msg} -> class_map
rules -> Map.put(class_map, class_name, List.wrap(rules))
end
end)
quote do
import LiveViewNative.Stylesheet.SheetParser, only: [sigil_SHEET: 2]
import LiveViewNative.Stylesheet.RulesParser, only: [sigil_RULES: 2]
import LiveViewNative.Stylesheet.RulesHelpers

@format unquote(format)
@before_compile LiveViewNative.Stylesheet

def compile_ast(class_or_list, target \\ [target: :all])
def compile_ast(class_or_list, target: target) do
class_or_list
|> List.wrap()
|> Enum.reduce(%{}, fn(class_name, class_map) ->
case class(class_name, target: target) do
{:unmatched, msg} -> class_map
rules -> Map.put(class_map, class_name, List.wrap(rules))
end
end)
end

def compile_string(class_or_list, target \\ [target: :all]) do
pretty = Application.get_env(:live_view_native_stylesheet, :pretty, false)

compile_ast(class_or_list, target)
|> inspect(limit: :infinity, charlists: :as_list, printable_limit: :infinity, pretty: pretty)
end
def compile_string(class_or_list, target \\ [target: :all]) do
pretty = Application.get_env(:live_view_native_stylesheet, :pretty, false)

class_or_list
|> compile_ast()
|> inspect(limit: :infinity, charlists: :as_list, printable_limit: :infinity, pretty: pretty)
end

def __native_opts__ do
%{format: unquote(format)}
end
end

{:error, message} -> raise message
def __native_opts__ do
%{format: unquote(format)}
end
end
end

Expand All @@ -53,7 +48,9 @@ defmodule LiveViewNative.Stylesheet do
Application.put_env(:live_view_native_stylesheet, :__sheet_paths__, [sheet_path | sheet_paths])

quote do
def class(_, _), do: {:unmatched, []}
def class(unmatched, target: target) do
{:unmatched, "Stylesheet warning: Could not match on class: #{inspect(unmatched)} for target: #{inspect(target)}"}
end
end
end
end
46 changes: 12 additions & 34 deletions lib/live_view_native/stylesheet/rules_parser.ex
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
defmodule LiveViewNative.Stylesheet.RulesParser do
@callback parse(rules :: binary, opts :: keyword()) :: list
@macrocallback __using__(format :: atom) :: tuple
defmacro sigil_RULES({:<<>>, _meta, [rules]}, _modifier) do
opts = [
file: __CALLER__.file,
line: __CALLER__.line + 1,
module: __CALLER__.module,
variable_context: nil
]

defmacro __using__(format) do
quote do
@behaviour LiveViewNative.Stylesheet.RulesParser

defmacro sigil_RULES(rules, _modifier) do
opts = [
file: __CALLER__.file,
line: __CALLER__.line + 1,
module: __CALLER__.module,
variable_context: nil
]
compiled_rules = EEx.compile_string(rules)

LiveViewNative.Stylesheet.RulesParser.parse(rules, unquote(format), opts)
end
quote do
LiveViewNative.Stylesheet.RulesParser.parse(unquote(compiled_rules), @format, unquote(opts))
end
end

Expand All @@ -32,32 +27,15 @@ defmodule LiveViewNative.Stylesheet.RulesParser do
def parse(body, format, opts \\ []) do
case fetch(format) do
{:ok, parser} ->
opts = opts
opts =
opts
|> Keyword.put_new(:variable_context, Elixir)
|> Keyword.update(:file, "", &Path.basename/1)

body
|> LiveViewNative.Stylesheet.Utils.eval_quoted()
|> String.replace("\r\n", "\n")
|> parser.parse(opts)
|> List.wrap()
|> Enum.map(&escape(&1))
{:error, message} -> raise message
end
end

defp escape({operator, meta, arguments}) when operator in [:<>] do
{operator, meta, Enum.map(arguments, &escape(&1))}
end
defp escape({Elixir, _meta, expr}), do: expr
defp escape({identity, annotations, arguments}) do
{:{}, [], [identity, annotations, escape(arguments)]}
end
defp escape([{key, value} | tail]) do
[{key, escape(value)} | escape(tail)]
end
defp escape([argument | tail]) do
[escape(argument) | escape(tail)]
end
defp escape(literal), do: literal
end
8 changes: 3 additions & 5 deletions lib/live_view_native/stylesheet/sheet_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,10 @@ defmodule LiveViewNative.Stylesheet.SheetParser do
module: __CALLER__.module
)

for {arguments, opts, body} <- blocks do
quote bind_quoted: [arguments: Macro.escape(arguments), body: body, opts: opts] do
ast = LiveViewNative.Stylesheet.RulesParser.parse(body, @native_opts[:format], opts)

for {arguments, _opts, body} <- blocks do
quote do
def class(unquote_splicing(arguments)) do
unquote(ast)
sigil_RULES(<<unquote(body)>>, [])
end
end
end
Expand Down
2 changes: 0 additions & 2 deletions lib/live_view_native/stylesheet/sheet_parser/parser/error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ defmodule LiveViewNative.Stylesheet.SheetParser.Parser.Error do
error_message,
opts
) do
# IO.inspect({[], rest, error_message}, label: "error[0]")

context =
Context.put_new_error(context, rest, %__MODULE__{
Expand All @@ -53,7 +52,6 @@ defmodule LiveViewNative.Stylesheet.SheetParser.Parser.Error do
error_message,
opts
) do
# IO.inspect({matched_text, rest, error_message}, label: "error[0]")

context =
Context.put_new_error(context, rest, %__MODULE__{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule LiveViewNative.Stylesheet.SheetParser.PostProcessors do
def block_open_with_variable_to_ast(rest, [variable, string], context, {line, _offset}, _byte_offset) do
{rest,
[
{:<>, context_to_annotation(context, line) ++ [context: Elixir, imports: [{2, Kernel}]],
{:<>, context_to_annotation(context, line) ++ [context: nil, imports: [{2, Kernel}]],
[string, variable]}
], context}
end
Expand All @@ -26,7 +26,7 @@ defmodule LiveViewNative.Stylesheet.SheetParser.PostProcessors do
[
[
class_name,
{:_target, context_to_annotation(context, line), Elixir}
{:_target, context_to_annotation(context, line), nil}
]
], context}
end
Expand All @@ -42,7 +42,7 @@ defmodule LiveViewNative.Stylesheet.SheetParser.PostProcessors do
end

def to_elixir_variable_ast(rest, [variable_name], context, {line, _offset}, _byte_offset) do
{rest, [{String.to_atom(variable_name), context_to_annotation(context, line), Elixir}],
{rest, [{String.to_atom(variable_name), context_to_annotation(context, line), nil}],
context}
end

Expand Down
47 changes: 16 additions & 31 deletions test/live_view_native_stylesheet_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ defmodule LiveViewNative.StylesheetTest do
doctest LiveViewNative.Stylesheet

test "will compile the rules for all listed classes" do
output = MockSheet.compile_ast(["color-blue", "color-red"], target: nil)
output = MockSheet.compile_ast(["color-blue", "color-yellow"], target: nil)

assert output == %{"color-blue" => [2], "color-red" => [1,3,4]}
assert output == %{"color-blue" => ["rule-2"], "color-yellow" => ["rule-yellow"]}
end

test "will compile the rules for a specific target" do
output = MockSheet.compile_ast(["color-blue", "color-red"], target: :watch)
output = MockSheet.compile_ast(["color-blue", "color-yellow"], target: :watch)

assert output == %{"color-blue" => [4,5], "color-red" => [1,3,4]}
assert output == %{"color-blue" => ["rule-5"], "color-yellow" => ["rule-yellow"]}
end

test "won't fail when an class name isn't found" do
Expand All @@ -21,58 +21,43 @@ defmodule LiveViewNative.StylesheetTest do
end

test "can compile without target, will default to `target: :all`" do
output = MockSheet.compile_ast(["color-blue", "color-red"])
output = MockSheet.compile_ast(["color-blue", "color-yellow"])

assert output == %{"color-blue" => [2], "color-red" => [1,3,4]}
assert output == %{"color-blue" => ["rule-2"], "color-yellow" => ["rule-yellow"]}
end

test "can compile for a single class name" do
output = MockSheet.compile_ast("color-blue")

assert output == %{"color-blue" => [2]}
end

test "can compile when a rule set is not a list" do
output = MockSheet.compile_ast("single")

assert output == %{"single" => [{:single, [], [1]}]}
assert output == %{"color-blue" => ["rule-2"]}
end

test "can compile custom classes using the RULES sigil" do
output = MockSheet.compile_ast("custom-123")

assert output == %{"custom-123" => [{:foobar, [], [1, 2, 123]}]}

output = MockSheet.compile_ast("custom-124")
assert output == %{"custom-124" => [{:foobar, [], [1, 2, 124]}]}
end
output = MockSheet.compile_ast("custom-123-456")

test "can compile custom classes using the RULES sigil (2)" do
output = MockSheet.compile_ast("custom-multi-123-456")
assert output == %{"custom-123-456" => ["rule-123", "rule-456"]}

assert output == %{"custom-multi-123-456" => [
{:foobar, [], [1, 2, 123]},
{:bazqux, [], [3, 4, 456]}
]}
output = MockSheet.compile_ast("custom-789-123")
assert output == %{"custom-789-123" => ["rule-789", "rule-123"]}
end

describe "LiveViewNative.Stylesheet sigil" do
test "single rules supported" do
output = MockSheet.compile_ast(["color-yellow"], target: :all)

assert output == %{"color-yellow" => [{:foobar, [], [1, 2, 3]}]}
assert output == %{"color-yellow" => ["rule-yellow"]}
end

test "multiple rules and class name pattern matching" do
output = MockSheet.compile_ast(["color-hex-123"], target: :all)
output = MockSheet.compile_ast(["color-number-4"], target: :all)

assert output == %{"color-hex-123" => ["rule-31-123", {:foobar, [], [1, 2, "123"]}]}
assert output == %{"color-number-4" => ["rule-1", "rule-24"]}
end

test "can convert the output to a string" do
output = MockSheet.compile_string(["color-hex-123"])
output = MockSheet.compile_string(["color-number-3"])

assert output == ~s(%{"color-hex-123" => ["rule-31-123", {:foobar, [], [1, 2, "123"]}]})
assert output == ~s(%{"color-number-3" => ["rule-1", "rule-23"]})
end
end
end
30 changes: 6 additions & 24 deletions test/rules_parser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,7 @@ defmodule LiveViewNative.Stylesheet.RulesParserTest do

result = RulesParser.parse(rules, :mock)

assert result == [1, 2]
end

test "will rewrite parsed rules for macro escape but will hoist Elixir terms" do
rules = """
rule-21
rule-22
rule-ime
"""

result = RulesParser.parse(rules, :mock)

assert result == [
{:{}, [], [:foobar, [], [1, 2, 3]]},
{:{}, [], [:foobar, [], [1, 2, {:number, [], Elixir}]]},
{:{}, [], [:color, [], [color: [{:{}, [], [:., [], [nil, :red]]}]]]}
]
assert result == ["rule-1", "rule-2"]
end

test "will raise when parser is not found" do
Expand All @@ -58,15 +42,13 @@ defmodule LiveViewNative.Stylesheet.RulesParserTest do

test "will pass annotation data through to the rules parser" do
rules = """
rule-21-annotated
rule-21
rule-annotated
"""

result = RulesParser.parse(rules, :mock, file: @file_path, line: 1, module: @module)

assert result == [
{:{}, [], [:foobar, [file: @file_name, line: 1, module: @module], [1, 2, 3]]},
{:{}, [], [:foobar, [], [1, 2, 3]]},
{:foobar, [file: @file_name, line: 1, module: @module], [1, 2, 3]}
]
end
end
Expand Down Expand Up @@ -95,7 +77,7 @@ defmodule LiveViewNative.Stylesheet.RulesParserTest do
input = "to_float(number)"

output =
{Elixir, annotation(1), {:to_float, annotation(1), [{:number, annotation(1), Elixir}]}}
{Elixir, annotation(1), {:to_float, annotation(1), [{:number, annotation(1), nil}]}}

assert {:ok, [result], _, _, _, _} =
parse_helper_function(input, file: @file_path, module: @module)
Expand All @@ -107,7 +89,7 @@ defmodule LiveViewNative.Stylesheet.RulesParserTest do
input = "to_abc(family)"

output =
{Elixir, annotation(1), {:to_abc, annotation(1), [{:family, annotation(1), Elixir}]}}
{Elixir, annotation(1), {:to_abc, annotation(1), [{:family, annotation(1), nil}]}}

assert {:ok, [result], _, _, _, _} =
parse_helper_function(input, file: @file_path, module: @module)
Expand All @@ -121,7 +103,7 @@ defmodule LiveViewNative.Stylesheet.RulesParserTest do
)"

output =
{Elixir, annotation(1), {:to_abc, annotation(1), [{:family, annotation(2), Elixir}]}}
{Elixir, annotation(1), {:to_abc, annotation(1), [{:family, annotation(2), nil}]}}

assert {:ok, [result], _, _, _, _} =
parse_helper_function(input, file: @file_path, module: @module)
Expand Down
Loading

0 comments on commit 77572c1

Please sign in to comment.