diff --git a/lib/slime/parser/embedded_engine.ex b/lib/slime/parser/embedded_engine.ex index 9614357..71c5224 100644 --- a/lib/slime/parser/embedded_engine.ex +++ b/lib/slime/parser/embedded_engine.ex @@ -6,7 +6,7 @@ defmodule Slime.Parser.EmbeddedEngine do @type parser_tag :: binary | {:eex | binary, Keyword.t} @callback render(binary, Keyword.t) :: parser_tag - import Slime.Compiler, only: [compile: 1] + import Slime.Parser.TextBlock, only: [render_content: 2] @engines %{ javascript: Slime.Parser.EmbeddedEngine.Javascript, @@ -16,37 +16,22 @@ defmodule Slime.Parser.EmbeddedEngine do } |> Map.merge(Application.get_env(:slime, :embedded_engines, %{})) |> Enum.into(%{}, fn ({key, value}) -> {to_string(key), value} end) + @registered_engines Map.keys(@engines) - def render_with_engine(engine, line_contents) when is_list(line_contents) do - lines = Enum.map(line_contents, &compile/1) - embedded_text = case lines do - [] -> "" - [line | _] -> - strip_indent = indent(line) - lines - |> Enum.map(&strip_line(&1, strip_indent)) - |> Enum.join("\n") - end + def parse(engine, lines) when engine in @registered_engines do + embedded_text = render_content(lines, 0) - render_with_engine(engine, embedded_text) + {:ok, render_with_engine(engine, embedded_text)} end - - def render_with_engine(engine, embedded_text) do - keep_lines = Application.get_env(:slime, :keep_lines) - embedded_text = if keep_lines do - "\n" <> embedded_text - else - embedded_text - end - apply(@engines[engine], :render, [embedded_text, [keep_lines: keep_lines]]) + def parse(engine, _) do + {:error, ~s(Unknown embedded engine "#{engine}")} end - defp indent(line) do - String.length(line) - String.length(String.lstrip(line)) - end + defp render_with_engine(engine, text) do + keep_lines = Application.get_env(:slime, :keep_lines) + text = if keep_lines, do: ["\n" | text], else: text - defp strip_line(line, strip_indent) do - String.slice(line, min(strip_indent, indent(line))..-1) + apply(@engines[engine], :render, [text, [keep_lines: keep_lines]]) end end @@ -57,7 +42,7 @@ defmodule Slime.Parser.EmbeddedEngine.Javascript do @behaviour Slime.Parser.EmbeddedEngine - def render(text, _options), do: {"script", children: [text]} + def render(text, _options), do: {"script", children: text} end defmodule Slime.Parser.EmbeddedEngine.Css do @@ -68,7 +53,7 @@ defmodule Slime.Parser.EmbeddedEngine.Css do @behaviour Slime.Parser.EmbeddedEngine def render(text, _options) do - {"style", attributes: [type: "text/css"], children: [text]} + {"style", attributes: [type: "text/css"], children: text} end end @@ -83,13 +68,18 @@ defmodule Slime.Parser.EmbeddedEngine.Elixir do def render(text, options) do newlines = if options[:keep_lines] do - count = text |> String.split("\n") |> length |> Kernel.-(1) + count = Enum.count(text, &Kernel.==(&1, "\n")) [String.duplicate("\n", count)] else [] end - %EExNode{content: text, children: newlines} + eex = Enum.map_join(text, fn + ({:eex, interpolation}) -> ~S"#{" <> interpolation <> "}" + (text) -> text + end) + + %EExNode{content: eex, children: newlines} end end diff --git a/lib/slime/parser/text_block.ex b/lib/slime/parser/text_block.ex index cc680fa..47ca9a2 100644 --- a/lib/slime/parser/text_block.ex +++ b/lib/slime/parser/text_block.ex @@ -41,7 +41,7 @@ defmodule Slime.Parser.TextBlock do defp insert_line_spacing(lines, text_indent) do concat_lines(lines, fn({line_indent, line_contents}, content) -> - leading_space = String.duplicate(" ", line_indent - text_indent) + leading_space = String.duplicate(" ", max(0, line_indent - text_indent)) case leading_space do "" -> ["\n" | line_contents ++ content] _ -> ["\n" | [leading_space | line_contents ++ content]] diff --git a/lib/slime/parser/transform.ex b/lib/slime/parser/transform.ex index 98f5719..ead7ef4 100644 --- a/lib/slime/parser/transform.ex +++ b/lib/slime/parser/transform.ex @@ -17,6 +17,8 @@ defmodule Slime.Parser.Transform do alias Slime.Parser.Nodes.InlineHTMLNode alias Slime.Parser.Nodes.DoctypeNode + alias Slime.TemplateSyntaxError + @default_tag Application.get_env(:slime, :default_tag, "div") @sort_attrs Application.get_env(:slime, :sort_attrs, true) @merge_attrs Application.get_env(:slime, :merge_attrs, %{"class" => " "}) @@ -142,24 +144,26 @@ defmodule Slime.Parser.Transform do end end - def transform(:text_block_line, [space, content], _index) do - {indent_size(space), content} + def transform(:embedded_engine, [engine, _, content], index) do + case EmbeddedEngine.parse(engine, content[:lines]) do + {:ok, {tag, content}} -> + %HTMLNode{name: tag, + attributes: (content[:attributes] || []), + children: content[:children]} + {:ok, content} -> content + {:error, message} -> + {{:line, line_number}, {:column, column}} = index + raise TemplateSyntaxError, message: message, + line: "", line_number: line_number, column: column + end end - def transform(:embedded_engine, [engine, _, content], _index) do - lines = content[:lines] - case EmbeddedEngine.render_with_engine(engine, lines) do - {tag, content} -> %HTMLNode{name: tag, - attributes: (content[:attributes] || []), - children: content[:children]} - content -> content - end + def transform(:embedded_engine_lines, [first_line, rest], _index) do + [first_line | Enum.map(rest, fn ([_, lines]) -> lines end)] end - def transform(:embedded_engine_lines, input, _index) do - [line, rest] = input - lines = Enum.map(rest, fn ([_, lines]) -> lines end) - [line | lines] + def transform(:indented_text_line, [space, content], _index) do + {indent_size(space), content} end def transform(:inline_html, [_, content, children], _index) do diff --git a/src/slime_parser.peg.eex b/src/slime_parser.peg.eex index 3cf0e82..5b6e0ea 100644 --- a/src/slime_parser.peg.eex +++ b/src/slime_parser.peg.eex @@ -82,15 +82,16 @@ code_comment <- '/' text_block; verbatim_text <- indent:space? type:[|'] content:text_block; -text_block <- text_block_line (crlf +text_block <- indented_text_line (crlf indent lines:text_block_nested_lines dedent)?; -text_block_nested_lines <- text_block_line (crlf ( +text_block_nested_lines <- indented_text_line (crlf ( indent lines:text_block_nested_lines dedent / lines:text_block_nested_lines ))*; -text_block_line <- space? text_item*; embedded_engine <- tag_name ':' (crlf indent lines:embedded_engine_lines dedent); -embedded_engine_lines <- text_item* (crlf text_item*)*; +embedded_engine_lines <- indented_text_line (crlf indented_text_line)*; + +indented_text_line <- space? text_item*; tag_name <- [a-zA-Z0-9_-]+; attribute_name <- [a-zA-Z0-9_@:-]+; diff --git a/test/rendering/embedded_engine_test.exs b/test/rendering/embedded_engine_test.exs index 8305083..6dd39c5 100644 --- a/test/rendering/embedded_engine_test.exs +++ b/test/rendering/embedded_engine_test.exs @@ -90,14 +90,16 @@ defmodule RenderEmbeddedEngineTest do end test "render embedded elixir" do - slime = """ + slime = ~S""" elixir: a = [1, 2, 3] |> Enum.map(&(&1 * &1)) - = Enum.join(a, ",") + b = "test" + c = " and #{b}" + = Enum.join(a, ",") <> c """ - assert render(slime) == ~s(1,4,9) + assert render(slime) == ~s(1,4,9 and test) end test "render embedded eex" do @@ -108,6 +110,17 @@ defmodule RenderEmbeddedEngineTest do assert render(slime) == ~s(Test: test) end + test "raises an error for unknown engines" do + assert_raise Slime.TemplateSyntaxError, + ~r/Unknown embedded engine \"textile\"/, fn -> + render """ + p + textile: + *Textile* is _easy_ to read and _easy_ to write + """ + end + end + defmodule TestEngine do @moduledoc false @behaviour Slime.Parser.EmbeddedEngine