From 62ff8329da19fc6901431e85c7a2726b5421159c Mon Sep 17 00:00:00 2001 From: Alexander Stanko Date: Thu, 13 Aug 2015 02:43:05 +0700 Subject: [PATCH] Add attribute merging support --- lib/slim_fast/parser.ex | 7 +++- lib/slim_fast/parser/attributes_keyword.ex | 46 ++++++++++++++++++++++ test/parser/attributes_keyword_test.exs | 36 +++++++++++++++++ test/parser_test.exs | 8 ++-- test/renderer_test.exs | 4 ++ 5 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 lib/slim_fast/parser/attributes_keyword.ex create mode 100644 test/parser/attributes_keyword_test.exs diff --git a/lib/slim_fast/parser.ex b/lib/slim_fast/parser.ex index 365b609..8554118 100644 --- a/lib/slim_fast/parser.ex +++ b/lib/slim_fast/parser.ex @@ -1,4 +1,6 @@ defmodule SlimFast.Parser do + alias SlimFast.Parser.AttributesKeyword + @blank "" @content "|" @comment "/" @@ -22,6 +24,8 @@ defmodule SlimFast.Parser do @tabsize 2 @soft_tab String.duplicate(" ", @tabsize) + @merge_attrs %{class: " "} + def parse_lines(lines) do parse_lines(Enum.map(lines, &use_soft_tabs/1), []) end @@ -171,8 +175,9 @@ defmodule SlimFast.Parser do additional = parts["attrs"] |> html_attributes children = parts["tail"] |> String.lstrip |> inline_children + attributes = AttributesKeyword.merge(basics ++ additional, @merge_attrs) - {tag, attributes: basics ++ additional, children: children} + {tag, attributes: attributes, children: children} end defp parse_tag(input) do diff --git a/lib/slim_fast/parser/attributes_keyword.ex b/lib/slim_fast/parser/attributes_keyword.ex new file mode 100644 index 0000000..6f6b810 --- /dev/null +++ b/lib/slim_fast/parser/attributes_keyword.ex @@ -0,0 +1,46 @@ +defmodule SlimFast.Parser.AttributesKeyword do + @doc """ + Merges multiply attributes values for keys specified in merge_rules. + Attribute value may be given by string, list, or {:eex, args} node + `merge_rules` should me an `%{attribute_name: joining_character}` map + ## Examples + iex> SlimFast.Parser.AttributesKeyword.merge( + ...> [class: "a", class: ["b", "c"], class: "d"], + ...> %{class: " "} + ...> ) + [class: "a b c d"] + + iex> SlimFast.Parser.AttributesKeyword.merge( + ...> [class: "a", class: ["b", "c"], class: {:eex, content: "d"}], + ...> %{class: " "} + ...> ) + [class: {:eex, content: ~S("a b c \#{d}"), inline: true}] + """ + def merge(keyword_list, merge_rules) do + Enum.reduce(merge_rules, keyword_list, fn ({attr, join}, result) -> + case Keyword.get_values(result, attr) do + [] -> result + values -> Keyword.put(result, attr, merge_attribute_values(values, join)) + end + end) + end + + defp merge_attribute_values(values, join_by) do + result = join_attribute_values(values, join_by) + if Enum.any?(values, &dynamic_value?/1) do + {:eex, content: ~s("#{result}"), inline: true} + else + result + end + end + + defp dynamic_value?({:eex, _}), do: true + defp dynamic_value?(_), do: false + + defp join_attribute_values(values, join_by) do + values |> Enum.map(&attribute_val/1) |> List.flatten |> Enum.join(join_by) + end + + defp attribute_val({:eex, args}), do: "\#{" <> args[:content] <> "}" + defp attribute_val(value), do: value +end diff --git a/test/parser/attributes_keyword_test.exs b/test/parser/attributes_keyword_test.exs new file mode 100644 index 0000000..46bef87 --- /dev/null +++ b/test/parser/attributes_keyword_test.exs @@ -0,0 +1,36 @@ +defmodule ParserAttributesKeywordTest do + use ExUnit.Case + doctest SlimFast.Parser.AttributesKeyword + + test "handles multiple eex nodes" do + result = SlimFast.Parser.AttributesKeyword.merge( + [class: "a", class: {:eex, content: "b"}, class: {:eex, content: "c"}], + %{class: " "} + ) + assert result == [class: {:eex, content: ~S("a #{b} #{c}"), inline: true}] + end + + test "supports custom delimiter" do + result = SlimFast.Parser.AttributesKeyword.merge( + [class: "a", class: "b"], + %{class: "--"} + ) + assert result == [class: "a--b"] + end + + test "leaves unspecified attributes as is" do + result = SlimFast.Parser.AttributesKeyword.merge( + [class: "a", id: "id", class: "b", id: "id1"], + %{class: " "} + ) + assert result == [class: "a b", id: "id", id: "id1"] + end + + test "handles all attributes specified in merge rules" do + result = SlimFast.Parser.AttributesKeyword.merge( + [class: "a", id: "id", class: "b", id: "id1"], + %{class: " ", id: "+"} + ) + assert result == [id: "id+id1", class: "a b"] + end +end diff --git a/test/parser_test.exs b/test/parser_test.exs index 383979c..8d41724 100644 --- a/test/parser_test.exs +++ b/test/parser_test.exs @@ -5,17 +5,17 @@ defmodule ParserTest do test "parses simple nesting" do parsed = ["#id.class", "\tp", "\t| Hello World"] |> Parser.parse_lines - assert parsed == [{0, {:div, attributes: [class: ["class"], id: "id"], children: []}}, {2, {:p, attributes: [], children: []}}, {2, "Hello World"}] + assert parsed == [{0, {:div, attributes: [class: "class", id: "id"], children: []}}, {2, {:p, attributes: [], children: []}}, {2, "Hello World"}] parsed = ["#id.class","\tp Hello World"] |> Parser.parse_lines - assert parsed == [{0, {:div, attributes: [class: ["class"], id: "id"], children: []}}, {2, {:p, attributes: [], children: ["Hello World"]}}] + assert parsed == [{0, {:div, attributes: [class: "class", id: "id"], children: []}}, {2, {:p, attributes: [], children: ["Hello World"]}}] end test "parses css classes with dashes" do {_, {:div, opts}} = ".my-css-class test" |> Parser.parse_line - assert opts == [attributes: [class: ["my-css-class"]], children: ["test"]] + assert opts == [attributes: [class: "my-css-class"], children: ["test"]] end test "parses attributes" do @@ -82,7 +82,7 @@ defmodule ParserTest do test "parses final newline properly" do parsed = ["#id.class", "\tp", "\t| Hello World", ""] |> Parser.parse_lines - assert parsed == [{0, {:div, attributes: [class: ["class"], id: "id"], children: []}}, {2, {:p, attributes: [], children: []}}, {2, "Hello World"}] + assert parsed == [{0, {:div, attributes: [class: "class", id: "id"], children: []}}, {2, {:p, attributes: [], children: []}}, {2, "Hello World"}] end test "parses html comments" do diff --git a/test/renderer_test.exs b/test/renderer_test.exs index 1ddddae..494a07c 100644 --- a/test/renderer_test.exs +++ b/test/renderer_test.exs @@ -202,4 +202,8 @@ defmodule RendererTest do assert render(slim) == ~s(
) end + + test "render tags with attrbiute merging" do + assert render(~s(.class-one class="class-two")) == ~s(
) + end end